Async `measure` in XCTest?

It is amazing that XCTest supports async await, by adding async to the test method signature.

However, it seams that there are no way to measure performance of async code? at least measure method lacks an async alternative.

Do you have any recommendations on how to proceed?

2 Likes

I’m wondering about this as well. This is as far as I got, but I get an EXC_BAD_ACCESS at the stopMeasuring() call.

self.measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
    Task {
        try await store.dispatch(TestAction())
        self.stopMeasuring()
    }
}

What happens if you wrap self.stopMeasuring in a Task { @MainActor [weak self] self?.stopMeasuring }?

My thinking here is that maybe XCTest related code ought to run on Main Thread? And maybe it wasn’t?

Same result with that unfortunately. Still fails on the stopMeasuring() call.

func testStoreDispatchPerformance() async throws {
    let store = try await Store(model: TestModel())

    self.measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        Task {
            try await store.dispatch(TestAction())
            Task { @MainActor [weak self] in
                self?.stopMeasuring() // EXC_BAD_ACCESS
            }
        }
    }
}

This works for me:

func testStoreDispatchPerformance() async throws {
    let store = try await Store(model: TestModel())

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        let exp = expectation(description: "Finished")
        Task {
            try await store.dispatch(TestAction())
            await MainActor.run {
                stopMeasuring()
            }
            exp.fulfill()
        }
        wait(for: [exp], timeout: 2.0)
    }
}
1 Like

Thanks for the suggestion! Unfortunately I didn’t get anywhere with this. I’m seeing Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Cannot stop measuring. -stopMeasuring is only supported from a block passed to -measure...Block.

You were very close. The following works for me.

    func testXXXX() throws {
      self.measure {
        let exp = expectation(description: "Finished")
        Task {
          // async code goes here
          exp.fulfill()
        }
        wait(for: [exp], timeout: 200.0)
      }
    }

5 Likes

Hello. Is it exp.fulfill() run in MainThread, isn't it?

The fulfill call needs to be in a separate task from the code in measure because measure is sync only. If the async code touches the main thread then the test (measure) has to be in a different one or it will hang.