Tag: asynchronous

  • iOS Testing: Handling Asynchronous Code

    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.