The Mental Blog

Software with Intellect

16 notes

Unit Testing Asynchronous Cocoa

I often hear developers new to unit testing express concern about how to deal with asynchronous operations. When I first began writing tests for Ensembles, I had the same concerns. In fact, testing asynchronous code is quite easy.

First, I should point out that unit testing advocates will advise you that an asynchronous method is more easily tested if it is factored into a synchronous–asynchronous pair of methods. You write the unit test for the synchronous method. The asynchronous version wraps a call to the synchronous method, initiating any threading, and invoking callbacks on completion.

This approach makes a lot of sense, and you should certainly consider it. But there will undoubtedly be times when you don’t want to partition the method, or you want to fully test the operation — asynchronicity and all. So how do you go about that?

There are two aspects to testing an asynchronous task that you need to consider. The first is that the unit test method should not return until the asynchronous task has fully completed; otherwise, the test will terminate prematurely. The second, which relates only to unit tests of Cocoa code, is to keep the main run loop turning over. Without the run loop, functionality such as networking and timers will not work.

The way I run asynchronous operations in unit tests for Ensembles is to include two methods in the test class. The first starts the run loop, and does not return until the run loop is stopped.

- (void)waitForAsynchronousTask
{
    CFRunLoopRun();
}

The second method explicitly stops the run loop, causing the first method to return.

- (void)completeAsynchronousTask
{
    CFRunLoopStop(CFRunLoopGetCurrent());
}

The first method is called at the end of the test method, and the second method is called inside the completion handler of the code being tested.

- (void)testCreateRemoteEventDirectoryFirstTime
{
    [cloudManager createRemoteDirectoryStructureWithCompletion:^(NSError *error) {
        XCTAssertNil(error, @"Error should be nil");
        [self completeAsynchronousTask];
    }];
    [self waitForAsynchronousTask];
}

That’s all there is to it. You can call waitForAsynchronousTask and completeAsynchronousTask multiple times in a single test. Just call waitForAsynchronousTask when you want to wait for an asynchronous task, and call completeAsynchronousTask when that task has finished.

Filed under cocoa objective-c unit-testing

  1. script-ease reblogged this from mentalfaculty
  2. christiantietze reblogged this from mentalfaculty
  3. hartlco reblogged this from mentalfaculty
  4. mandrakeinrochester reblogged this from mentalfaculty
  5. attila reblogged this from mentalfaculty and added:
    So simple. Got to rewrite those tests now.
  6. mentalfaculty posted this