I'm trying to update my asynchronous unit tests to use the new XCTestExpectation
interface instead of manually spinning the run loop.
My unit tests previously utilized the functions waitForBlock
, finishBlock
, and waitForTimeInterval:
which is simply a convenience method that called finishBlock
after the specified time. I'm trying to update this setup to use expectations.
The tests that were utilizing waitForBlock
+ finishBlock
semantics are all working just as expected after being replaced with waitForExpectationsWithTime:handler:
and fulfill
, but my solution to replace waitForTimeInterval:
doesn't seem to be working.
- (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:
Seems like that code actually does work... so this was probably just Xcode 6 screwing with me this afternoon.
I feel like it should be fairly straight-forward: Create an expectation, set up an asynchronous block that fulfills is, and wait. However, the dispatch_after
block is never invoked.
My hunch is that waitForExpectationsWithTimeout:handler:
blocks its current thread, which is the main queue, so the run loop never gets around to its asynchronous blocks. That seems reasonable, but I'm having trouble coming up with a different way to implement this functionality.
I'm looking for either 1) additional information about XCTestExpectation
that might reveal a workaround, or 2) a different idea for implementing this functionality.
Timely: Unit tests should be written just before the production code that makes the test pass. This is something that you would follow if you were doing TDD (Test Driven Development), but otherwise it might not apply.
Still, it seems as though a 10 second short-term attention span is more or less hard-wired into the human brain. Thus, a unit test suite used for TDD should run in less than 10 seconds. If it's slower, you'll be less productive because you'll constantly lose focus.
The primary class for defining test cases, test methods, and performance tests. Xcode 7.2+
Run all your unit tests as often as possible, ideally every time the code is changed. Make sure all your unit tests always run at 100%. Frequent testing gives you confidence that your changes didn't break anything and generally lowers the stress of programming in the dark.
In one of my Unit test cases I needed to test the running of a method in my main app code, which should trigger a timer in about 1 second to call another method in the app. I used XCTestExpectation
wait and DispatchQueue.asyncAfter
as a mechanism to stop and wait before I check the result. The following code is a snippet in Swift 3 / 4:
<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>
dispatch_async(dispatch_get_main_queue(), ...)
doesn't seem to work in Xcode 7 UI Tests, the completion handler is never called. performSelector
is no longer available in Swift, but there are two other workarounds:
Using a 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 on global queue (it works, but probably unsafe as it's 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)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With