Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit Tests for designs that use notifications

I'm having difficulty testing some logic that uses notifications. I've read about enforcing that particular NSNotifications are sent, but that doesn't really address the problem I'm seeing.

[SomeObject PerformAsyncOperation] creates an NSURLRequest and sets itself as the response delegate. Depending on the content of the response, SomeObject posts a success or failure NSNotification to the default NSNotificationCenter.

The problem with my test is that after PerformAsyncOperation is called, the test doesn't wait for the response to be sent. Instead, it continues on with the assert - which fails, because the request/response hasn't had time to be sent/received/parsed.

Here is the code:

-(void)testMethod {
    SomeObject *x = [[SomeObject alloc] init];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                        selector:@selector(success:)
                                            name:SomeObjectSuccess
                                          object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                        selector:@selector(failure:)
                                            name:SomeObjectFailure
                                          object:nil];

    operationCompleted = false; // declared in .h

    [x PerformAsyncOperation:@"parameter"];

    STAssertTrue(operationCompleted , @"Operation has completed.");

    [[NSNotificationCenter defaultCenter] removeObserver:self name:SomeObjectSuccess object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:SomeObjectFailure object:nil];

    [x release];
}

-(void) failure:(NSNotification *) notification {
   operationCompleted = true;
   NSLog(@"Received failure message.");
}

-(void) success:(NSNotification *) notification {
   operationCompleted = true;
   NSLog(@"Received success message.");
}

I've tried sleeping the thread after the call to PerformAsyncOperation, as well as an empty while (!operationCompleted) loop. Neither works - sleeping the thread still fails the assert, and the while loop never exits. I've also tried moving the asserts into success: and failure:, but because OCUnit expects each method to be a test method, it just executes all three methods immediately and quits (without waiting for the NSURLRequest's response, which is the whole point of the exercise!).

Any ideas or insight would be greatly appreciated. Thanks!

like image 531
Daniel A. Thompson Avatar asked Jun 01 '11 17:06

Daniel A. Thompson


1 Answers

The problem you face is not notifications, which are synchronous. Rather, it is that you are firing off an asynchronous operation. To make this a repeatable test, you need to resynchronize things.

NSTimeInterval timeout = 2.0;   // Number of seconds before giving up
NSTimeInterval idle = 0.01;     // Number of seconds to pause within loop
BOOL timedOut = NO;

NSDate *timeoutDate = [[NSDate alloc] initWithTimeIntervalSinceNow:timeout];
while (!timedOut && !operationCompleted)
{
    NSDate *tick = [[NSDate alloc] initWithTimeIntervalSinceNow:idle];
    [[NSRunLoop currentRunLoop] runUntilDate:tick];
    timedOut = ([tick compare:timeoutDate] == NSOrderedDescending);
    [tick release];
}
[timeoutDate release];

Beyond this point, you know that either the operation completed, or the test timed out.

like image 185
Jon Reid Avatar answered Oct 15 '22 03:10

Jon Reid