How to unit test asynchronous APIs?

asynchronous, google-toolbox-for-mac, iphone, unit-testing

Solution

I ran into the same question and found a different solution that works for me.

I use the "old school" approach for turning async operations into a sync flow by using a semaphore as follows:

// create the object that will perform an async operation
MyConnection *conn = [MyConnection new];
STAssertNotNil (conn, @"MyConnection init failed");

// create the semaphore and lock it once before we start
// the async operation
NSConditionLock *tl = [NSConditionLock new];
self.theLock = tl;
[tl release];    

// start the async operation
self.testState = 0;
[conn doItAsyncWithDelegate:self];

// now lock the semaphore - which will block this thread until
// [self.theLock unlockWithCondition:1] gets invoked
[self.theLock lockWhenCondition:1];

// make sure the async callback did in fact happen by
// checking whether it modified a variable
STAssertTrue (self.testState != 0, @"delegate did not get called");

// we're done
[self.theLock release]; self.theLock = nil;
[conn release];

Make sure to invoke

[self.theLock unlockWithCondition:1];

In the delegate(s) then.

Problem

I have installed Google Toolbox for Mac into Xcode and followed the instructions to set up unit testing found here. It all works great, and I can test my synchronous methods on all my objects absolutely fine. However, most of the complex APIs I actually want to test return results asynchronously via calling a method on a delegate - for example a call to a file download and update system will return immediately and then run a -fileDownloadDidComplete: method when the file finishes downloading. How would I test this as a unit test? It seems like I'd want to the testDownload function, or at least the test framework to 'wait' for fileDownloadDidComplete: method to run. EDIT: I've now switched to using the XCode built-in XCTest system and have found that TVRSMonitor on Github provides a dead easy way to use semaphores to wait for async operations to complete. For example: ``` - (void)testLogin { TRVSMonitor *monitor = [TRVSMonitor monitor]; __block NSString *theToken; [[Server instance] loginWithUsername:@"foo" password:@"bar" success:^(NSString *token) { theToken = token; [monitor signal]; } failure:^(NSError *error) { [monitor signal]; }]; [monitor wait]; XCTAssert(theToken, @"Getting token"); } ```

Original source

Related problems