Asynchronous Unit Tests

AHHHHHH!When I created the project for RFRddMe I added Unit Tests. Every developer should do this today. They’re easy to create and Xcode does a great job getting you started. Everything is setup, you just need to write and run the tests. Really great stuff!

The only challenge for me was how to test asynchronous code? As far as I know it’s up to the developer to figure that out. That’s fine, I have a solution, but I thought I’d share it and see if anyone has an opinion about how I did it.

If you haven’t used the Unit Test Framework included with Xcode, you should. It’s a cut of SenTestingKit, and it does the trick.

It provides a couple of methods that allow you to setup and tear down a unit of tests and all you need to do is write test methods. Here’s an example test method, that does nothing.

- (void)test;
    STAssertTrue(true, @"test");

So, how in the world do you test asynchronous code? The way you write any other asynchronous code. You need a way to block until the code you’ve executed is finished.

The RFRddMe projects makes use of ASIHttpRequest, which runs in a thread. This project requires the implementation of an protocol, RFRddMeProtocol, which provides a mechanism for your application to receive results. This is all really cool because your application’s UI will remain responsive while you wait for results, but I digress.

The bottom line is we have to block while we wait on our results to return. So, here’s what I did to make this work. I created a method, I dubbed hackyRunLoopWait, that makes use of the default NSRunLoop to wait for completion of our asynchronous methods to complete. Here’s what it looks like.

// Introduced methods.
- (BOOL)hackyRunLoopWait;
    // Reset our _done flag.
    _done = NO;
    // Reset the _succeeded flag to NO.
    _succeeded = NO;
    // Get the current run loop.
    NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
    // Spin until the _done is YES or we've waited for 90 seconds.
    // Yep, it's a complete hack, just to get this working.
    while ((NO == _done) && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:90]]);
    // Need to deal with exiting the loop above because we timed out.
    return _succeeded;

Here’s how I used it to test methods on RFRddMe.

- (void)testShorten;
    RFRddMe* p = [[RFRddMe alloc] initWithDelegate:self];
    [p shortenUrl:@""];
    STAssertTrue([self hackyRunLoopWait], @"testShorten");
    [p release];    

Notice the STAssertTrue() statement, it contains [self hackyRunLoopWait], which blocks and waits on our shortenUrl method to complete.

Another thing to make note of is RFRddMeProtocol is implemented by the test class, see the [[RFRddMe alloc] initWithDeelgate:self]. When the request completes it will notify the shortenSucceeded method, which sets the values monitored in hackyRunLoopWait, which allows that method to return.

// RFRddMeProtocol Implementation
- (void)shortenSucceeded:(RFRddMeShortUrl*)shortUrl;
    _succeeded  = YES;
    _done       = YES;

Is this the best way? I don’t know, but it does what I need it to do, and it works as expected. If someone has a better idea I’d love to hear from you. Please, leave a comment, or send an email;