Unit Testing Objective-C asychronous block-based API
March 10, 2014Unit Testing Objective-C block-based API
We are starting to utilize more Unit Testing for business logic and behavior design that’s going on under the hood in our apps.
Using Objective-C blocks for asynchronous operations in Apple’s Cocoa framework is an increasingly popular pattern. Built-in libraries such as Core Animation & Core Motion and 3rd-party libraries like AFNetworking utilize blocks extensively for asynchronous callbacks.
NSDictionary *params = @{@"id":@2}; [[MLStore sharedStore] GETWithParameters:params completionBlock:^void(id responseObject, NSError *err) { // do something with responseObject passed by block XCTAssert(responseObject != nil, @"GET response should not be nil."); }];
This will succeed every time because, most likely, the test case will have terminated by the time the block completes/returns to the caller. The test never fails because the Assert will never even be evaluated. So what’s the solution? Stack Overflow to the rescue!
My favorite (and simplest to understand, in my opinion) answer from that SO question is to use Grand Central Dispatch and the built-in semaphore constructs.
NSInterval TIMEOUT = 5.0; // create a semaphore with initial value of 0 dispatch_semaphore_t sem = dispatch_semaphore_create(0); [myConcurrentAPI doSomethingWithCompletion:^(id data, NSError *error) { // check error and do stuff with data dispatch_semaphore_signal(sem); // signal to OS to release/decrement semaphore }]; // check for the Semaphore signal but keep executing the main // run loop for TIMEOUT seconds while (dispatch_semaphore_wait(sem, DISPATCH_TIME_NOW)) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:TIMEOUT]]; }
The program will execute the method with the aynchronous block, then stick at the while loop but continue executing the main run loop (i.e. not blocking execution of the main or other threads.) Once the block returns/completes, it signals the semaphore (a little bit of shared memory managed by the runtime/operating system) and your test method continues or completes.
If you need test multiple async calls in the same method, I recommend creating a new semaphore variable each time – don’t reuse an exisiting one.
Here are three C preprocessor macros that simplify the code a bit. You still need to provide a variable name but semicolons are optional.
#define SemaphoreSetup(SEM_NAME) dispatch_semaphore_t SEM_NAME = dispatch_semaphore_create(0); #define SemaphoreSignal(SEM_NAME) dispatch_semaphore_signal(SEM_NAME); #define SemaphoreWait(SEM_NAME) while (dispatch_semaphore_wait(SEM_NAME, DISPATCH_TIME_NOW)) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:TIMEOUT]]; } /* Then use like this */ SemaphoreSetup(sem) [[MLStore sharedStore] GETWithParameters:params completionBlock:^void(id responseObject, NSError *err) { // do something with responseObject passed by block XCTAssert(responseObject != nil, @"GET response should not be nil."); SemaphoreSignal(sem) }]; SemaphoreWait(sem)
Happy Testing!
Stay in the loop with our latest content!
Select the topics you’re interested to receive our new relevant content in your inbox. Don’t worry, we won’t spam you.

Michigan Software Labs joins the Inc. 5000 list of fastest growing companies in the U.S.
August 12, 2020Michigan Software Labs has earned its first recognition in Inc. magazine’s influential Inc. 5000 list. The list represents a unique look at the most successful companies within the American economy’s most dynamic segment—its independent small businesses.
Read more
How we approach UX @ MichiganLabs
July 29, 2019At Michigan Software Labs, we focus on building superior user experiences (UX) for our clients and their customers.
Read more
2022 Best and Brightest Winners in West Michigan
May 2, 2022The Best and Brightest Companies to Work For® competition identifies and honors organizations that display a commitment to excellence in operations and employee enrichment that lead to increased productivity and financial performance!
Read more