Testing iOS modules: expect a time interval with a wait

I am trying to update my asynchronous unit tests to use the new XCTestExpectation interface instead of manually starting the run loop.

My unit tests previously used the functions waitForBlock , finishBlock and waitForTimeInterval: which are just a convenient method called finishBlock after the specified time. I am trying to update this setting to use expectations.

Tests using waitForBlock + finishBlock semantics work just as expected after replacing with waitForExpectationsWithTime:handler: and fulfill , but my solution to replace waitForTimeInterval: does not seem to work.

 - (void)waitForTimeInterval:(NSTimeInterval)delay { XCTestExpectation *expectation = [self expectationWithDescription:@"wait"]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [expectation fulfill]; }); [self waitForExpectationsWithTimeout:delay + 1 handler:nil]; } 

Edit:

It seems that this code really works ... so it was probably just the Xcode 6 that it burst into with me this afternoon.


I feel this should be fairly straightforward: create a wait, configure an asynchronous block that executes and wait. However, the dispatch_after block is never called.

My hunch is that waitForExpectationsWithTimeout:handler: blocks its current thread, which is the main queue, so the loop cycle never gets close to its asynchronous blocks. This seems reasonable, but I'm having problems with another way to implement this functionality.

I'm looking for either 1) additional information about XCTestExpectation , which may reveal a workaround, or 2) another idea for implementing this function.

+7
ios unit-testing xcode6
source share
4 answers

I used XCTestExpectation in several projects for all kinds of asynchronous network and GCD stuff ... AFAIK, something like the following, which seems to be the most common use:

 - (void)testExample { // create the expectation with a nice descriptive message XCTestExpectation *expectation = [self expectationWithDescription:@"The request should successfully complete within the specific timeframe."]; // do something asyncronously [someObject doAsyncWithCompletionHandler:^(NSInteger yourReturnValue) { // now that the async task it done, test whatever states/values you expect to be after this is // completed NSInteger expectedValue = 42; XCTAssert(yourReturnValue == expectedValue, @"The returned value doesn't match the expected value."); // fulfill the expectation so the tests can complete [expectation fulfill]; }]; // wait for the expectations to be called and timeout after some // predefined max time limit, failing the test automatically NSTimeInterval somePredefinedTimeout = 3; [self waitForExpectationsWithTimeout:somePredefinedTimeout handler:nil]; } 
+5
source share

dispatch_async(dispatch_get_main_queue(), ...) doesn't seem to work in XIode 7 UI Tests, the completion handler is never called. performSelector no longer available in Swift, but there are other ways:

  • Using timer

     var waitExpectation: XCTestExpectation? func wait(duration: NSTimeInterval) { waitExpectation = expectationWithDescription("wait") NSTimer.scheduledTimerWithTimeInterval(duration, target: self, selector: Selector("onTimer"), userInfo: nil, repeats: false) waitForExpectationsWithTimeout(duration + 3, handler: nil) } func onTimer() { waitExpectation?.fulfill() } 
  • Run the block in the global queue (it works, but is probably unsafe since it is not documented anywhere that XCTestExpectation is thread safe).

     func wait(duration: NSTimeInterval) { let expectation = expectationWithDescription("wait") let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(duration * Double(NSEC_PER_SEC))) dispatch_after(dispatchTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { expectation.fulfill() } waitForExpectationsWithTimeout(duration + 3, handler: nil) } 
+8
source share

Apparently using performSelector:withObject:afterDelay: works as expected, although I'm still not sure why. If anyone has an idea why GCD dispatch_after does not work, provide an additional answer and I will agree with him. At the moment, this setting works as expected:

 - (void)waitForTimeInterval:(NSTimeInterval)delay { XCTestExpectation *expectation = [self expectationWithDescription:@"wait"]; [self performSelector:@selector(fulfillExpectation:) withObject:expectation afterDelay:delay]; [self waitForExpectationsWithTimeout:delay + 1 handler:nil]; } - (void)fulfillExpectation:(XCTestExpectation *)expectation { [expectation fulfill]; } 
+4
source share

In one of my Unit test cases, I needed to test the method in the main code of the application, which should call the timer in about 1 second to call another method in the application. I used XCTestExpectation wait and DispatchQueue.asyncAfter as a stop and wait mechanism before checking the result. The following code is a snippet in Swift 3:

  <call the main app method which will trigger a timer event> // wait let expectation = XCTestExpectation(description: "test") DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) { expectation.fulfill() } wait(for: [expectation], timeout: 2.5) <check the result of the timer event> 
0
source share

All Articles