iOS Testing: Handling Asynchronous Code

asynchronous
Credit: Flickr / davidjoyner

I was handling a common occurrence when doing something that takes a noticeable amount of time on iOS.

  1. Show loading UI.
  2. Do work.
  3. Transition to post-work UI.

To make the experience better (so that it doesn’t lock up), I pushed 2 onto the background thread, and then had to push 3 back on to the main thread – this is one of The Rules of iOS, all UI stuff has to happen on the main thread.

And then my tests started failing. Aha! A learning opportunity! (Also known as, super annoying as I had other things to get done that day).

Kind friends directed me to the  XCTest feature of writing tests with asynchronous operations, which seemed a little confusing at first but is essentially:

  1. Create a thing (an “Expectation”).
  2. Include a timeout in your test that will allow it time to happen.
  3. Call “fulfil” on that expectation when your thing has happened.

Create The Thing

In your test class:

XCTestExpectation *expectation = [self expectationWithDescription:@"desc"];

Add The Timeout

After you call the function under test (before asserts, verifying mocks etc):

[self waitForExpectationsWithTimeout:1 handler:nil];

You might want to include a handler in the block, this wasn’t necessary for my purposes.

Calling Fulfil

This code is easy! It’s just:

[expectation fulfill];

The question was, where to put it? The examples seemed to put it in a completion block, but if you look back at my outline above, the end of my test was when the UI changes to the post-activity UI. Specifically, when a new ViewController is pushed on the stack.

So, I used a feature of the mocking framework (OCMock), that I can stub a method and then do something when it is called. So here, I stub the pushViewController method, because it’s the last thing that should get called, and when it is I set my expectation to be fulfilled.

OCMStub([mockNavController pushViewController:[OCMArg any]
                                       animated:YES])
      .andDo(^(NSInvocation *invocation){
          [expectation fulfill];
      });

Voila!

 

For more detail on this and other aspects of iOS unit-testing (including unit-testing UI code!) you might find my digital workshop helpful.

5 thoughts on “iOS Testing: Handling Asynchronous Code

  1. Aha! I should try that, I have been rolling my own code to do something fairly similar.

    [WORDPRESS HASHCASH] The poster sent us ‘0 which is not a hashcash value.

Leave a Reply