I’m working on an iOS app that pulls data down from the TheyWorkForYou API, and because it’s the Right Thing To Do, I’m attempting to build it test-first. That’s not as straight-forward as it perhaps should be with Objective-C, and nor is it made any easier by the asynchronous nature of a lot of the iOS SDK. So this post is by way of an outboard brain explanation for a) me, when I’ve forgotten what I did in a couple of weeks and b) anyone else who stumbles across this through the magic of Google.
The app is communicating with the API through a standalone class which can be used anywhere within the app. I’m using the AFNetworking library, so I’ve created a singleton as a subclass of AFHTTPclient.
This client calls the API using a block-based syntax and has a success block and a failure block. These blocks are executed asynchronously on a background thread, so in order for the comms class to pass data back to the rest of the application, it does so through a delegate protocol. The API client declares a protocol with a method called apiRepliedWithResponse that takes a single parameter (either the data that’s been received from the API, or nil in the case of errors).
This method is implemented in the API client’s delegate, and gets called from within the success and failure blocks. Any object that wishes to use the API client needs to implement the delegate protocol, and handle the responses from the API client. Testing this ****Testing this throws up some interesting problems - I’m using the Kiwi testing framework which has a nice RSpec-like syntax as well as all the testing bells and whistles that you might need.
The first issue is that the API client is making asynchronous calls - therefore you need to add a delay inbetween calling the method under test, and making any assertions about the results. If the assertion is made too quickly, the asynchronous method won’t have had time to complete, and the assertion will fail.
This problem gets dealt with by Kiwi’s shouldEventually assertion, of which more shortly. The second issue is what to test? There are two things that need to be checked - firstly that the delegate method gets called at all; and secondly that the correct parameter is passed back. The process here is to create a mock object that pretends to conform to the API client’s protocol, then set this mock object as the API client’s delegate.
We can then set an assertion that the delegate method gets called; and check that the data which is returned is in fact what we were expecting. So here’s the process in detail:
- create a mock that “conforms” to the API client’s delegate protocol
1
| |
- create an object containing the data that will be sent back
e.g. if it’s an NSString of value ”foo” that’s expected back, you’ll need to create an NSString of value ”foo”. In my case, I’m asserting that I’m getting back an NSData representation of a JSON file:
1 2 | |
- set the mock as the API client’s delegate:
1
| |
- set the assertion that the mock should *eventually* be called with the method and response object (using Kiwi, this is
shouldEventually):
1
| |
- call the method under test:
1
| |
To remove the dependency on the API itself, I’m using the OHHTTPStubs library - this intercepts any calls to the network inside the API client, and returns a canned response:
1 2 3 4 5 | |
Here’s the full test:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | |
And the method that it’s testing:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | |
No doubt there are far more elegant ways of achieving the same results, but this is now working nicely.