Home > Blockchain >  Why this test case hangs when run on a simulator?
Why this test case hangs when run on a simulator?

Time:12-22

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.

CodePudding user response:

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.

CodePudding user response:

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.

  • Related