XCTestExpectation在Task中调用fulfill仍超时的问题如何解决
问题原因
- 核心原因是你在async测试方法中使用了同步的
wait(for:timeout:)API:Swift并发使用的协作式线程池不允许线程阻塞,同步wait会直接占用当前执行测试的并发线程,导致你创建的两个Task无法获得调度资源执行,自然不会走到fulfill()逻辑,最终触发超时。你观测到catch块执行、fulfill被调用大概率是加了断点调试时,断点暂停了阻塞的线程,给了Task调度执行的机会,正常运行时不会出现这个情况。 - 次要原因(如果你的逻辑会触发两次CancellationError):默认
XCTestExpectation的expectedFulfillmentCount为1,且触发超过1次fulfill会默认报错,如果你预期会有两次错误抛出,需要主动修改这个配置。
解决方案
方案1:使用async测试专用的异步等待API(推荐)
直接用Swift Concurrency适配的fulfillment(of:timeout:)异步方法,不会阻塞并发线程:
func testCancellation() async throws { // setup code let expectation = XCTestExpectation(description: "Cancellation Error thrown") // 如果预期会触发2次取消错误,加上下面这行 // expectation.expectedFulfillmentCount = 2 let autocompleteHandler = AutocompleteHandler(session: session) Task() { do { let _ = try await autocompleteHandler.autocomplete(query: "Grizzly Bear") } catch { if error is CancellationError { expectation.fulfill() } } } Task() { do { let _ = try await autocompleteHandler.autocomplete(query: "Grizzly Bear") } catch { if error is CancellationError { expectation.fulfill() } } } // 替换原来的同步wait为异步等待API await fulfillment(of: [expectation], timeout: 1) }
方案2:改为非async测试方法
如果不想改动太多原有逻辑,可以把测试方法的async修饰符去掉,同步测试方法的运行队列不属于Swift并发线程池,调用wait(for:timeout:)不会阻塞Task调度:
// 去掉async修饰符 func testCancellation() throws { // 其余原有代码保持不变 }
可选优化配置
如果你的逻辑存在多次触发fulfill的可能,可以加上下面的配置避免测试报错:
// 允许超过预期次数的fulfill,不会抛出异常 expectation.assertForOverFulfill = false
内容的提问来源于stack exchange,提问作者Naknut
相关产品推荐
相关产品推荐

