Unit tests are very useful for those large projects where you might loose sight of everything that’s going on and while you are adding new code to add feature X you might be silently breaking feature Y without even suspecting. It’s super easy these days to add unit tests to your Xcode project – when you create new Xcode project just check the checkbox in the save dialogue and voila everything is setup automatically for you:
This will add unit testing to your project and when you click Product->Test from Xcode’s menu your unit test will be run. Cool and smooth.
Let’s have a look at the default unit test, which is created for you- you get a folder called “[Project name]Tests” and inside there’s one test pre-set for you. Open the .m file and find the only test inside:
- (void)testExample { STFail(@"Unit tests are not implemented yet in utb_testTests"); } |
It’s designed so that it’ll fail by default. Eventually if you run the tests (Cmd+U) you’ll see the tests fail (though my experience shows even this simple example test will succeed for no reason many times):
So in this article I’m not going to explain how unit tests work and so on, but I’d rather cover how to overcome some obstacles if you are writing unit tests for block based APIs.
Unlike your iPhone apps the unit test suite is being started, it runs the code of all tests and then when there’s nothing more to run it just exists. If it didn’t spit any exception, the test was successful. End of story.
So let’s see how that pairs up with using blocks. Let’s replace the content of the test method with this code:
- (void)testExample { CLGeocoder* gc = [[CLGeocoder alloc] init]; [gc geocodeAddressString:@"Hermannplatz, Berlin, Germany" completionHandler:^(NSArray *placemarks, NSError *error) { STFail(@"Failed inside a block"); }]; } |
This code creates a new geocoder and asks for matches for the given address. Since the API is asynchronous the results are fetched inside the provided block. You’ll need to also add an import at the top of the file:
#import <CoreLocation/CoreLocation.h> |
and of course add the CoreLocation.framework to the project.
Hit Cmd+U and … the test passes. Inevitably and always.
So why does this happen?
The test suit doesn’t wait for and don’t know that you are waiting for geocoding results from Apple’s server. It runs all the code and then when there’s no more code to run – exits.
Problem?
You actually need to make the application hang around alive until you get the results for the geocoding server and only tell it “i finished my work you can exit now please”. But naturally if you just tell the app to sleep it won’t be able to react when the results come back from the server …
Some diggin’ through StackOverflow finds me this solution which I really like:
http://stackoverflow.com/a/4326754/208205
dispatch_semaphore_t sema = dispatch_semaphore_create(0); [object runSomeLongOperationAndDo:^{ STAssert… dispatch_semaphore_signal(sema); }]; dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); dispatch_release(sema); |
Using Grand Central Dispatch and its C functions you can create a semaphore object and wait till the signal comes in. However there are 2 things I don’t like in this code:
- using C functions and
- I have rather complex class structure of classes, so passing reference to the semaphore is rather inconvenient.
So I quickly came up with a simple class to implement my own semaphore. It has two handy methods – waitForKey:(NSString*)key and lift:(NSString*)key … and here’s how you use it:
- (void)testExample { CLGeocoder* gc = [[CLGeocoder alloc] init]; [gc geocodeAddressString:@"Hermannplatz, Berlin, Germany" completionHandler:^(NSArray *placemarks, NSError *error) { STFail(@"Failed inside a block"); [[TestSemaphore sharedInstance] lift:@”geocode1”]; }]; [[TestSemaphore sharedInstance] waitForKey:@”geocode1”]; } |
Pretty straight forward – and since it’s a singleton class you can call it from wherever you want. Also it is free and available for download if you want to tingle with unit testing. And hey – it’s a semaphore, so you can use it for whatever purpose suits you
Marin
The post was originally published on the following URL: http://www.touch-code-magazine.com/unit-testing-for-blocks-based-apis/
·






Here’s an alternative way, see my answer: http://stackoverflow.com/questions/2162213/how-to-unit-test-asynchronous-apis
Thomas, the code of the semaphore does use a runloop – it’s just packaged so that it’s easier to call it and you don’t have to clutter your test methods with code. Marin
Will use in my tests.
Thanks for sharing your solution Marin, it works great. I ran into one small issue if you reuse keys (e.g. if you use TestSemaphor in setUp and tearDown). I’ve added one line that removes the key automatically after use, which fixes the issue.
-(void)waitForKey:(NSString*)key
{
BOOL keepRunning = YES;
while (keepRunning && [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]) {
keepRunning = ![[TestSemaphor sharedInstance] isLifted: key];
}
[self.flags removeObjectForKey:key]; // Remove key
}
Very cool! Unfortunately, I suspect this is subject to the same deadlocking condition mentioned in the original SO answer if the callback is executed on the main queue, which is common to perform UI operations in a callback. (Unfortunate since its a nice clean method for testin.)