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.


Posted

in

,

by

Comments

5 responses to “iOS Testing: Handling Asynchronous Code”

  1. qwandor Avatar

    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.

    1. Cate Avatar

      🙂 lmk how you get on!

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

  2. […] iOS Testing: Handling Asynchronous Code […]

    [WORDPRESS HASHCASH] The comment’s server IP (69.163.242.185) doesn’t match the comment’s URL host IP (69.163.242.203) and so is spam.