简体   繁体   中英

In XCTest: how to test that a function forced execution onto main thread

In the UI class I have a method that accesses UI elements, and hence is supposed to force itself onto a main thread. Here's a minimal example of what I mean:

class SomeUI {

    func doWorkOnUI() {

        guard Thread.isMainThread else {

            DispatchQueue.main.async {

                self.doWorkOnUI()
            }
            return
        }

        print("Doing the work on UI and running on main thread")
    }
}

In the tests, of course there's no problem to test the case when doWorkOnUI() is already running on main thread. I just do this:

func testWhenOnMainThread() {

    let testedObject = SomeUI()
    let expectation = XCTestExpectation(description: "Completed doWorkOnUI")

    DispatchQueue.main.async {
        testedObject.doWorkOnUI()
        expectation.fulfill()
    }

    wait(for: [expectation], timeout: 10.0)

    // Proceed to some validation
}

That is: force execution onto main thread. Wait for it to complete. Do some checks.

But how to test the opposite case, ie ensure that function forced itself to run on main thread when called from the background thread?

For example if I do something like:

...
    DispatchQueue.global(qos: .background).async {
        testedObject.doWorkOnUI()
        expectation.fulfill()
    }
...

I just tested that function got executed from the background thread. But I didn't explicitly check that it ran on main thread. Of course, since this function accesses UI elements, the expectation is that it crashes if not forced on main thread. So is "no crash" the only testable condition here? Is there anything better?

When there is an outer closure in the background and an inner closure on the main thread, we want two tests:

  1. Call the outer closure. Do a wait for expectations. Wait for 0.01 seconds. Check that the expected work was performed.
  2. Call the outer closure. This time, don't wait for expectations. Check that the work was not performed.

To use this pattern, I think you'll have to change your code so that the tests can call the outer closure directly without having to do an async dance already. This suggests that your design is too deep to be testable without some changes.

Find a way for an intermediate object to capture the closure. That is, instead of directly calling DispatchQueue.global(qos: .background).async , make a type that represents this action. Then a Test Spy version can capture the closure instead of dispatching it to the background, so that your tests can invoke it directly. Then you can test the call back to main thread using async wait.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM