3

I can't seem to figure out how to test this method:

- (void)writer:(id)writer didFailWithError:(NSError *)error;
{    
 [self.alertView dismissWithClickedButtonIndex:0 animated:YES];

  void (^alertViewBlock)(int) = ^(int buttonIndex)
  {
    if (buttonIndex == 1)
    {
        [self performSelectorOnMainThread:@selector(savePostponeReasonsAsynchronously) withObject:nil waitUntilDone:NO];
    }
    else
    {
        NSLog(@"dismissed");

        self.savePostponeReasonsQueue = nil;
    }
  };

 [self showPostponeReasonFailedAlert:alertViewBlock];
}

Specifically how do I test that the selector savePostponeReasonsAsynchronously was called?

thanks

jimijon
  • 2,046
  • 1
  • 20
  • 39

2 Answers2

4

One way to test asynchronous method calls is to wait for them to complete:

__block BOOL isRunning = YES;
[[[myPartialMock expect] andDo:^(NSInvocation *invocation){ isRunning = NO; }] savePostponeReasonsAsynchronously];
myPartialMock writer:nil didFailWithError:nil; 

NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:10];
do {
  [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                           beforeDate:timeout];
} while (isRunning);

STAssertFalse(isRunning, @"Test timed out.");
[myPartialMock verify];

This is a technique I learned by looking at Rob Napier's test code for RNCryptor, which also has some nice tricks using semaphores.

Ben Flynn
  • 18,524
  • 20
  • 97
  • 142
  • Just to help me more. Let's ignore the async call and instead concentrate passing a buttonIndex of 0, how do I test: self.savePostponeReasonsQueue = nil;Do I create a new block like you are doing? – jimijon Sep 08 '13 at 19:12
  • If you want to test the block, you can stub the method that calls the block, grab the block argument from the NSInvocation, then call it with the value you want to test with. See my answer here: http://stackoverflow.com/a/17707394/449161 – Ben Flynn Sep 08 '13 at 21:28
  • That was very helpful... had to add the NSInvocation category #import "NSInvocation+OCMAdditions.h" – jimijon Sep 09 '13 at 00:32
0

I've written a library implementing a "Promise". It turned out that this concept is not only useful for solving complex asynchronous problems, but also for testing:

Suppose, you have some async method and want to check whether a completion handler will be called, and whether it returns the expected result. As a bonus, you also want to set a timeout which the tester waits for a completion handler. If it it expires, the test should fail.

A generic completion hander block:

typedef void (^completion_block_t)(id result);

Your testie, an async method which may be run loop based:

- (void) asyncFooWithCompletion:(completion_block_t)completionHandler;

Your test may look as follows:

- (void) testAsyncFooWithCompletion 
{
    // Your test - executing on the main thread.

    RXPromise* handlerPromise = [RXPromise new];

    [foo asyncFooWithCompletion:^(id result){
        // possibly perform assertions         
        ...

        if (result is expected) {
            // expected in this test scenario
            // resolve promise with a value indicating success:
            [handlerPromise fulfillWithValue:@"OK"];
        }
        else {
            // unexpected in this test scenario
            // resolve promise with an error:
            [handlerPromise rejectWithReason:@"Unexpected result"]; 
        }
    }];

Note: asyncFooWithCompletion:s workload may be scheduled on a run loop, which executes on the corresponding thread - e.g. the main thread.

The completion handler may also be executed on the same run loop, e.g. on the main thread.

    // Set a timeout. If the timeout expires before the handler get called, 
    // the promise will be resolved with a "timeout error":
    [handlerPromise setTimeout:5.0];


    // Register handlers, which get called when _handlerPromise_ will be resolved.
    // We did perform a all assertions in the completion handler. Thus here, 
    // just catch unexpected failures - including a timeout:
    [handlerPromise.thenOn(dispatch_get_main_queue(), nil, 
    ^id(NSError* error) {
        // We are on the main thread
        ADD_FAILURE() << [[error description] UTF8String];
        return error;
     })  
     runLoopWait]; // wait on the current run loop.

     // if we reach here, assertions have been executed (on the main thread) 
     // and all handlers have been returned.

}    // end test
CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67