skip to Main Content

Consider this simple test case

func test_Example() async {
    let exp = expectation(description: "It's the expectation")
        
    Task {
        print("Task says: Hello!")
        exp.fulfill()
    }
        
    wait(for: [exp], timeout: 600)
}

When I run this on an actual device, the test passes. However, when I run it on a simulator, it hangs and never completes.

Removing the async keyword from the function declaration solves the issue and the test passes on a simulator as well.

My actual use case / tests require the test to be async, so removing it is not really an option.

2

Answers


  1. Chosen as BEST ANSWER

    Solution: Change wait(for:) to await waitForExpectations(timeout:)

    I don't know why it works, but probably it has something to do with the limited amount of threads available in a simulator.

    waitForExpectations(timeout:) is marked with @MainActor and wait(for:) is not.


  2. The mistake here doesn’t really have anything to do with testing; it seems to have to do with what async/await is.

    In your "simple test case", you have mistakenly added async to a method that is not async — that is, it doesn’t make any await calls to an async method. This has nothing to do with tests, really; it’s just a wrong thing to do in general, even though the compiler does not help you by warning you about it.

    On the contrary, the chief purpose of a Task block is usually to let you call an async method in a context that is not async. That is why, when you take away the async designation, everything goes fine; you are behaving a lot more correctly.

    However, the example still suffers from the problem that you aren’t doing anything async even inside the Task. A more appropriate use of a Task would look more like this:

        func test_Example() {
            let exp = expectation(description: "It's the expectation")
    
            Task {
                try await Task.sleep(for: .seconds(3))
                print("Task says: Hello!")
                exp.fulfill()
            }
    
            wait(for: [exp], timeout: 4)
        }
    

    I have deliberately configured my numbers so that we are actually testing something here. This test passes, but if you change the 4 in the last line to 2, it fails — thus proving that we really are testing whether the Task finished in the given time, and failing if it doesn’t.

    If you’re going to mark a test as async, then you would not use a Task inside it; you are already in a Task! But if we do that, then there is no need to wait for any expectations at all; the word await already waits:

        func test_Example() async throws {
            print("Task starts")
            try await Task.sleep(for: .seconds(3))
            print("Task says: Hello!") // three seconds later
        }
    

    But at this point we are no longer testing anything. And that’s sort of the point; there isn’t anything about your original async example that is async, so it remains unclear why we here in the first place.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search