There are two ways to get blocks dispatched to the main queue to run. The first is via dispatch_main
, as mentioned by Drewsmits. However, as he also noted, there’s a big problem with using dispatch_main
in your test: it never returns. It will just sit there waiting to run any blocks that come its way for the rest of eternity. That’s not so helpful for a unit test, as you can imagine.
Luckily, there’s another option. In the COMPATIBILITY
section of the dispatch_main
man page, it says this:
Cocoa applications need not call dispatch_main(). Blocks submitted to
the main queue will be executed as part of the “common modes” of the
application’s main NSRunLoop or CFRunLoop.
In other words, if you’re in a Cocoa app, the dispatch queue is drained by the main thread’s NSRunLoop
. So all we need to do is keep the run loop running while we’re waiting for the test to finish. It looks like this:
- (void)testDoSomething {
__block BOOL hasCalledBack = NO;
void (^completionBlock)(void) = ^(void){
NSLog(@"Completion Block!");
hasCalledBack = YES;
};
[MyObject doSomethingAsyncThenRunCompletionBlockOnMainQueue:completionBlock];
// Repeatedly process events in the run loop until we see the callback run.
// This code will wait for up to 10 seconds for something to come through
// on the main queue before it times out. If your tests need longer than
// that, bump up the time limit. Giving it a timeout like this means your
// tests won't hang indefinitely.
// -[NSRunLoop runMode:beforeDate:] always processes exactly one event or
// returns after timing out.
NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:10];
while (hasCalledBack == NO && [loopUntil timeIntervalSinceNow] > 0) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:loopUntil];
}
if (!hasCalledBack)
{
STFail(@"I know this will fail, thanks");
}
}