Unit Testing Objective-C asychronous block-based API

March 10, 2014

Unit 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!
Chris Carr
Chris Carr
Infrastructure Engineer

Looking for more like this?

Sign up for our monthly newsletter to receive helpful articles, case studies, and stories from our team.

Quickly Prototyping a Ktor HTTP API
Development Web

Quickly Prototyping a Ktor HTTP API

August 18, 2022

Whether it’s needing a quick REST API for a personal project, or quickly prototyping a mockup for a client, I like to look for web server frameworks that help me get up and running with minimal configuration and are easy to use. I recently converted a personal project’s API from an Express web server to a Ktor web server and it felt like a breeze. I’ll share below an example of what I found and how easy it is to get a Ktor server up and running.

Read more
Web app vs. mobile app: How to decide which is best for your business
Business Development

Web app vs. mobile app: How to decide which is best for your business

March 26, 2024

When considering whether to develop a web app or a mobile app for your business, there’s honestly no definitive answer. But this article will help you make an informed decision that aligns with your business goals and sets you up for success.

Read more
MichiganLabs’ approach to product strategy: Driving software success
Process Team

MichiganLabs’ approach to product strategy: Driving software success

February 12, 2024

Read more
View more articles