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"); } ```