2

I've recreated the example from here: http://www.mokacoding.com/blog/testing-callbacks-in-swift-with-xctest/.

I want to test for a timeout using waitForExpectations(). This should mimic a long running process that has timed out. To do this, I've set a sleep() command in the called function that is longer than the timeout in waitForExpectations().

However, the sleep() doesn't have any effect. The test always passes. I've tried putting sleep() before completion(true) as well but that doesn't change the outcome (i.e., passed test).

Any ideas what I'm doing run to trigger a test failure on timeout?

class SomeService {
    func doSomethingAsync(completion: (_ success: Bool) -> ()) {
        completion(true)
        sleep(5)
    }
}

In test class

let service = SomeService()
service.doSomethingAsync { (success) in
    XCTAssertTrue(success, "assert is true")
    expect.fulfill()
}

waitForExpectations(timeout: 3) { (error) in
    if let error = error {
        XCTFail("timeout errored: \(error)")
    }
}
4thSpace
  • 43,672
  • 97
  • 296
  • 475

1 Answers1

1

Your test passes because you are calling completion before sleep, so your expectation is being fulfilled almost immediately - before you wait for 5 seconds; while the completion block is executed asynchronously, it is likely going to finish in under a second.

If you call sleep insidecompletion then your test will fail as expected. However, your test may crash if the test is no longer running when expect.fulfill() is called since expect may no longer exist by the time it is executed, as it may have been cleaned up as soon as the test fails (about 2 seconds before the expectation will be fulfilled).

class SomeService {
    func doSomethingAsync(completion: (_ success: Bool) -> ()) {
        DispatchQueue.main.async {
            completion(true)
        }
    }
}

Test:

let service = SomeService()
service.doSomethingAsync { (success) in
    XCTAssertTrue(success, "assert is true")
    sleep(5)
    expect.fulfill()
}

waitForExpectations(timeout: 3) { (error) in
    if let error = error {
        XCTFail("timeout errored: \(error)")
    }
}
Oletha
  • 7,324
  • 1
  • 26
  • 46
  • As mentioned already, putting `sleep()` before `completion` has no effect. – 4thSpace Jun 22 '17 at 13:48
  • Yes that makes sense sorry, I have reworded my answer to be correct - you need to call sleep from the asynchronous completion block rather than in doSomethingAsync, which is synchronous so the sleep will have already executed before the test starts waiting. – Oletha Jun 22 '17 at 14:00
  • How would you recode the above so `sleep()` is called in `completion`? – 4thSpace Jun 22 '17 at 17:47
  • Updated answer with example. I also noticed you need to call the completion block asynchronously, added that too. – Oletha Jun 22 '17 at 18:09
  • Awesome answer! I had tried what you have minus the `DispatchQueue.main.async{}`. Why does that make all the difference? – 4thSpace Jun 22 '17 at 18:29
  • Without using the dispatch queue, all the code is being executed on the same thread, so there is only ever one thing happening at the same time. Methods won't execute asynchronously unless you specifically ask it to, which is what Dispatch is for. This way, you can proceed with the test while the completion block executes at the same time. :) – Oletha Jun 22 '17 at 18:32