I've recently been doing some iOS development, and working out the best way to test-drive the development of iOS apps was high on my priority list. I know that the automated testing of iOS applications is still not widely practiced and isn't well documented, so I decided to write a series of posts to start to rectify that. You may wish to read part 1 first.

Kiwi

We were looking for a testing framework which supported iOS’s asynchronous programming model and Kiwi answered the call. It has a great syntax, comprehensive set up assistance, asynchronous support and built in mocking. I’d highly recommend you check it out: the syntax helps me to think in the right way and it has pretty much all the features we needed.

Kiwi’s block syntax looks like this:

describe(@"Team", ^{
context(@"when newly created", ^{
it(@"should have a name", ^{
id team = [Team team];
[[team.name should] equal:@"Black Hawks"];
});
});
});

Much better than the old fashioned xUnit style of testing, in my opinion. You might hate it, of course. You can use Kiwi’s features without having to use the block syntax if you want.

Objective-C’s delegate model

Many of the Apple core libraries use a delegate pattern for handling callbacks from a class. This is similar to Java’s interfaces, and superficially similar to blocks in Ruby and anonymous functions in Javascript.

As an example, let’s take CoreLocation. When wanting to find the location of a phone, you create a new CoreLocationManager and call startUpdatingLocation on it:

CLLocationManager *manager = [[CLLocationManager alloc] init];
[manager startUpdatingLocation];

This call returns immediately: so how do you execute code when the location is found? You use a delegate: an object with responds to the locationManager: didUpdateToLocation: fromLocation method:

-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
NSLog("$@ I AM IN YOU", newLocation);
foundLocation = YES;
}

Then you set this object to be the CLLocationManager’s delegate before calling startUpdatingLocation. Often you set the delegate to self and define the delegate method on the calling object.

CLLocationManager *manager = [[CLLocationManager alloc] init];
manager.delegate = self;
[manager startUpdatingLocation];

There’s more about this model in this article from Apple.

Testing delegates

This is tricky to test, because we can’t simply do this:

it("should call the delegate when ready", ^{
[testObject startUpdatingLocation];
[[testObject.foundLocation should] equal:theValue(YES)];
});

The test will call startUpdatingLocation, and then immediately check the foundLocation property to see whether it’s been set. It won’t have been, because the delegate won’t have been called yet.

How were we to stub endpoints such as the location system for for our app? We found two ways of doing this, with varying effectiveness:

  • Using Objective-C categories to redefine class methods
  • Using a Kiwi stub to inject a derived class which mocks out key methods

Next post, I’ll dive into some detail on both of these methods and show some of the pros and cons of each.

How are you testing iPhone apps? Do chime in throughout the series with suggestions and comments, and I’ll edit the posts as appropriate.