Using XCTWaiter

Introduced in Xcode 8.3, XCTWaiter gives a richer API to wait for expectations.

Consider the below code snippet.

let expectationA = self.expectation(description: "Expectation A")
let expectationB = self.expectation(description: "Expectation B")
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {       
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
wait(for: [expectationA, expectationB], timeout: 3.0, enforceOrder: true)

This will obviously fail. But you do not know why it failed. It could have been due to any of these reasons.

  1. All the expectations did not complete in time.
  2. The expectations did not complete in expected order (obviously expectationB completed before expectationA).

And there is of course the trouble that this will report a failure even if you did not want it to. Assume your real tests, assertions, etc. only begin after a successful fulfillment.

The same thing when waited using XCTWaiter would look like this.

let result = XCTWaiter.wait(for: [expectationA, expectationB], timeout: 3.0, enforceOrder: true)
switch result {
case .completed: print("Everything fulfilled")
case .incorrectOrder: print("Unexpected order")
case .timedOut: print("Everything did not fulfill")
default: print("There was an issue")

So this tells you exactly what the reason was for expectations not getting fulfilled and also it does not automatically generate an error. If you wish, you can report an assertion yourself.

And XCTWaiter does have an associated delegate too which can be used every time an expectation fulfills or fails. A lot like completion handlers of waiting methods in good old XCTestCase. So you still don’t lose out on anything.

TLDR — Anytime a single test method has multiple expectations that aren’t mutually exclusive, wait and see if XCTWaiter would be better instead. Or if you do not want a failure to be reported for an unfulfilled expectation, just use XCTWaiter.

Another useful tip — Another default behavior with XCTestCase is that it continues to evaluate the tests even if a test fails. This may not always be desirable. If you know that a certain test failure will inevitably lead to failure of all subsequent tests, you can just halt the execution there itself by setting continueAfterFailure property to false in setUp. Its default value is true.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.