-
Notifications
You must be signed in to change notification settings - Fork 599
OHHTTPStubs and asynchronous tests
As OHHTTPStubs is a library generally used in Unit Tests invoking network requests, those tests are generally asynchronous.
This means that the network requests are generally sent asynchronously, in a separate thread or queue different from the thread used to execute the test case.
As a consequence, you will have to ensure that your Unit Tests wait for the requests to finish (and have their response arrived) before performing the assertions in your Test Cases, because otherwise your code making the assertions will execute before the request had time to get its response.
For example, this won't work, because sendAsynchronousRequest:queue:completionHandler: will return immediately (triggering the networking request in the background) and thus the testFoo method will reach its end before the completionHandler block had time to be called.
So your test framework will see that the test didn't trigger any assertion failure and will mark the test as succeeded, even if the completionHandler block is called later and trigger a (late) assertion failure because data is nil, but it would be too late.
- (void)testFoo
{
NSURLRequest* request = ...
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse* response, NSData* data, NSError* error)
{
NSAssertNotNil(data, @"Received data should not be nil");
}
];
// The rest of the code below will continue to execute without waiting for the request to have its response
}The correct way to write your Unit tests it thus to wait for the asynchronous request to have its response before doing any assertion on it. One way to do it is to make your test loop using a while loop — which executes [[NSRunLoop currentRunLoop] runUntilDate:...] or CFRunLoopRunInMode to be sure the main RunLoop continue to run — and only breaks if a flag is set to YES or a timeout is reached. Then in your completionHandler simple set the aforementioned flag to YES to let the test know that it can continue and check its assertions.
- (void)testFoo
{
NSURLRequest* request = ...
__block BOOL responseArrived = NO;
__block NSData* receivedData = nil;
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse* response, NSData* data, NSError* error)
{
receivedData = data;
responseArrived = YES;
}
];
// Wait for the asynchronous code to finish and for completionHandler block to be called… or timeout
NSDate* timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeout];
while (!responseArrived && ([timeoutDate timeIntervalSinceNow]>0))
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.01, YES);
// By the time we reach this code, the while loop has exited
// so the response has arrived or the test has timed out
NSAssertNotNil(data, @"Received data should not be nil");
}There are multiple solutions around the net to help you with such problems.
-
OHHTTPStubsuses a customAsyncSenTestCasesubclass ofSenTestCasethat provides methods likewaitForAsyncOperationWithTimeout:andnotifyAsyncOperationDone. You can freely use thisAsyncSenTestCasesubclass for your own tests as well - Other Test Frameworks provide everything needed to handle asynchronous test cases, like
Specta/Expectathat provide thewilloperator