iOS Integration Tests with Kiwi

Jonah Williams ·

I have been using Kiwi to run my iOS projects’ tests. Kiwi works great for defining BDD style unit tests which express the sort of nested assertions I like to write.

Unit tests should test code in isolation so that they remain small, fast, and stable. So my unit tests mock or stub any network calls or calls to other components, assert that those calls were made correctly, and invoke callbacks where necessary.

I recently wanted to add a set of integration tests which would verify that the app I was building could successfully interact with a remote API. These are certainly not unit tests; they are comparatively slow to run, they are fragile as they depend on a functing remote server configued in a known configuration, and they excercise several application components working together. They are also a great way to verify that the API version which the app communicates with still works as expected. Without such tests changes to the client or server could brake behaviors we rely on.

Setup

I created a new logic test target in my application to contain the integration tests. This isolated them from my unit tests and allowed me to run the fast unit tests frequently and switch to the slow integeration tests when necessary.

Since this was a logic test target it was also easy to add an additional scheme which allowed me to run the integration tests from the command line and in my CI environment. (See running xcode units tests from the command line)

Testing

Once I could run tests I had to find a way to setup application state for my tests. For synchronous processes this is no problem. However the components I wanted to tests added network requests to operation queues. I wanted to allow those requests to execute before performing assertions on their results.

Kiwi includes some support for this sort of asynchronous testing with a shouldEventually matcher:

[[expectFutureValue(fetchedData) shouldEventually] beNonNil];

That was great for simple assertions. It didn’t help when I want to assert that fetchedData was not only non-nil but was also a dictionary containing a particular set of keys and values. Nor did it help when I needed to manage the state of remote resources; creating a new resource, asserting it existed, updating it, deleting it, and verifying that the delete succeeded.

For those cases I needed a tool which would allow me to pause the tests until some asynchronous process finished.

My solution was the following category: