Asynchronous Testing with SenTestingKit

Xcode includes a testing library which is useful for unit testing.  Simple unit tests are easy to manage, but it tends to be (A) perform operation, and (B) test the state of your objects.

I was recently doing some asynchronous programming and wanted to build some tests against my code.  A little searching led me to a good idea by akisute on Github.  But I thought it could use some simplification.

It boils down to a single method that takes a block to determine if the expected action is completed. Simply implement it to return YES when it is.

- (NSTimeInterval)waitTillReady:(BOOL (^)())isReady timeout:(NSTimeInterval)seconds {
  NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate];
  NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:seconds];

  while (isReady() == NO && [loopUntil timeIntervalSinceNow] > 0) {
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:loopUntil];
  }

  NSTimeInterval finish = [NSDate timeIntervalSinceReferenceDate];
  return (finish - start);
}

In line 6 above, NSRunLoop performs the magic. In any iOS or OS X there is a run loop at the top of the call stack that gets things running in the event-driven applications. Instead of allowing it to drop out of our call stack back to the top level, we are creating a secondary run loop that allows anything the user causes or external events cause to be processed.

Once our event occurs or the timeout is reached, we return and let the normal execution of the main run loop eventually take back over.

And here’s an example use of it. Creating a message object which is simply loading a URL from the web. Here’s the code to test that it starts loading within 5 seconds, and that it completes within 20 seconds.

- (void)testMessage {
  PPPostMessage *message = [[PPPostMessage alloc] init];
  STAssertTrue(message.isRequesting, @"should automatically start requesting (currently)");

  NSTimeInterval delay = [self waitTillReady:^BOOL{
    return message.isLoading;
  } timeout:5];
  STAssertTrue(message.isLoading, @"expecting it to start loading in 5 second");
  NSLog(@"[%@ %@] load statup delay = %f", NSStringFromClass([self class]), NSStringFromSelector(_cmd), delay );

  delay = [self waitTillReady:^BOOL{
      return message.isLoaded;
    } timeout:20 - delay];
  
  STAssertTrue(message.isLoaded, @"expecting it to be loaded in 20 seconds");
  NSLog(@"[%@ %@] load time = %f", NSStringFromClass([self class]), NSStringFromSelector(_cmd), delay );
}

You’ll notice that the waitTillReady: method returns the time that it actually took and we are using that value to adjust the time we allow on the second test.

Tracking Through iPhone Source

There are many better ways to trace through your code in any environment than with print statements.  If there’s not, you need to change jobs (or convince them to choose an environment that won’t waste all your time chasing bugs).

Your first order of business should be to familiarize yourself with your debugger.

However, there are times that you want to track through the code without having to break.  Watching what is going on in some event code is a good example.

You may often want to print out what method you are executing when in Cocoa Touch (or plain Cocoa for that matter).  If you are new to the iPhone and/or Objective-C you may not be aware of the two “silent arguments” that get sent to every message that dispatched in the Objective-C runtime.

You can think of the methods as C functions that receive all the arguments you see in the Objective-C code, plus two others.  The first is self, which you should be familiar with.  You may have thought it was a language keyword with supernatural powers.  In reality, it is the first argument to your method.  It’s always named self, and it’s of type id – i.e. it can be any kind of object.

The other is a selector, which is type SEL. It is named _cmd, and just like self you can use it anytime you’re inside a method. Though the preceding underscore should give you a hint that it’s use is expected only in uncommon circumstances.

All this is a long walk back to the point. If you want to print this information out, rather that retyping every method name, and mistyping them, and not changing them when the names change, and copy-paste-erroring all over the place, you can have cleaner and more maintainable code using the _cmd value.

A SEL is not an object, it is an opaque type. So we’ll need to convert it into something we can print. There’s a nice “function” defined for that.

Some example uses:

NSLog(@"[%@ %@]", [self className], NSStringFromSelector(_cmd));
NSLog(@"%s: [%@ %@]", __FILE__, [self className], NSStringFromSelector(_cmd));
NSLog(@"%s %d: [%@ %@]", __FILE__, __LINE__, [self className], NSStringFromSelector(_cmd));

Of course the first example won’t tell you what file it’s in. You may have a subclass instance running through a superclass’ code. If you really want to know the file or the line number, the later examples show you how to take advantage of the C precompiler values to do that.

There are a variety of other uses for selectors. Objective-C’s dynamic dispatching can allow some very powerful behaviors with them. But that’s for another day.

Note: [object className] is not defined in the iPhone on-board library.  You’ll have to replace the [self className] with NSStringFromClass([self class]) for it to run natively on the iPhone OS.