Asynchronous iOS Unit Test Tutorial

By default, iOS unit tests in Xcode execute just like any other method, from top to bottom, in serial order. This is fine most of the time. Occasionally though, you’ll find the need to write a unit test for asynchronous code. And with the prevalence of closures in Swift, writing an asynchronous iOS unit test will become even more common place.

The Method To Test

Consider this method under test:

class Parser {

  func parse(toParse: String, success: () -> Void, failure: () -> Void) {
    // code omitted
  }

}

It requires some imagination, but envision that this is some kind of complicated parsing routine that takes a long amount of time to complete. Depending on the outcome of parsing the input, either a closure success() or ‘failure()` (that are provided to the method) are guaranteed to be called at some asynchronous, non-deterministic point in the future.

Why This Is Hard To Test

At the core of this method, an input string will be parsed, probably on a different thread. In some cases that will pass, and in some cases that will fail. We need to figure out how to write tests to verify that.

Initially, one might think to try a test like this:

func testParse_Succeeds() {
  let toTest = Parser()
  toTest.parse("Something that will parse", success: {
    // do nothing, test will pass
  }) { 
    // if failure parsing, fail test
    XCTFail()
  }
}

The problem with this approach, is that assuming the long running code in parse(_:success:failure) is executed on another thread, `testParse_Succeeds()’ will likely complete before the parsing actually completes, thus never giving the test the chance to perform the actual verification. This will result in false positives, with no way to actually see the test fail.

asynchronous iOS unit test

The Solution – Expectations

There’s a really cool API provided in an XCTestCase extension that makes testing asynchronous code possible.

public func expectationWithDescription(description: String) -> XCTestExpectation

and

public func waitForExpectationsWithTimeout(timeout: NSTimeInterval, handler: XCWaitCompletionHandler?)

Here’s how you can use this with the previous example to verify the asynchronous code:

func testParse_Succeeds() {
  // 1
  let expectation = expectationWithDescription("Parsing Succeeds")
  let toTest = Parser()
  toTest.parse("Something that will parse", success: {
      // 2
      expectation.fulfill()
    }) { 
      // 3
      XCTFail()
  }

  // 4
  waitForExpectationsWithTimeout(1.0) { (_) -> Void in
  }
}

Looking at this line by line:

  1. Create an expectation for parsing to succeed
  2. When parsing succeeds, mark the expectation as fulfilled (and optionally perform any other verification)
  3. Explicitly fail the test if parsing does not succeed
  4. Tell XCTest to wait 1.0 second for the expectation to be fulfilled, or otherwise fail the test. (Good thing the timeout is configurable).

That’s it, now you can write an asynchronous iOS unit test!

Getting Cleaner

Now you know how to write an asynchronous iOS unit test. Take a minute to try out these sweet extensions on XCTestCase. I think you’ll find a lot of creative uses for them. Keep in mind, it doesn’t necessarily need to be long running code that needs this solution, but rather any code that is going to execute in an asynchronous fashion. Do you unit test your web service API calls?

Happy cleaning.

Other References: