iOS单元测试:如何等待主队列异步任务完成后执行断言?
解决主队列异步任务的单元测试等待问题
这确实是单元测试里异步代码常见的头疼问题!别慌,就算没有可直接挂钩的回调,我们也有两种靠谱的办法让测试等主队列的异步任务跑完再做断言:
方法一:用XCTestExpectation绑定主队列任务末尾
我们可以利用主队列的任务执行顺序,把期望的完成操作放到主队列任务的最后,确保handleResp里的异步代码先执行完毕:
func testhandleResp() { subject = SomeClass() // 创建测试期望 let mainQueueTaskExpectation = XCTestExpectation(description: "等待主队列异步任务执行完成") // 调用目标处理方法 subject.handleResp(successResponse) // 把期望完成操作加到主队列任务末尾,确保前面的异步任务先跑完 DispatchQueue.main.async { mainQueueTaskExpectation.fulfill() } // 等待期望完成,设置合理的超时时间(比如1秒,足够这类同步任务执行) wait(for: [mainQueueTaskExpectation], timeout: 1.0) // 现在断言就会在异步任务完成后执行 XCTAssertNotNil(GlobalStaticStruct.code) }
原理很简单:主队列是串行队列,handleResp里的异步任务先被加入队列,我们后续加的fulfill操作会排在它后面,只有前面的任务执行完,这个闭包才会触发,测试也就会等到正确的时机再做断言。
方法二:强制清空主队列待处理任务
如果觉得用期望有点繁琐,还可以直接让主队列的RunLoop运行一次,把所有待处理任务都执行完:
func testhandleResp() { subject = SomeClass() subject.handleResp(successResponse) // 强制主队列执行完所有已排队的异步任务 RunLoop.main.run(until: Date()) // 此时异步任务已经执行完毕,安全做断言 XCTAssertNotNil(GlobalStaticStruct.code) }
这个方式更直接,适合确定主队列里没有无限循环任务的场景——它会让当前测试线程的RunLoop运行到当前时间点,把队列里所有等待的任务都清掉,自然就包括handleResp里的那个异步操作。
额外小建议
如果之后有重构代码的机会,最好给handleResp加个可选的完成回调,这样测试会更规范、耦合度更低:
func handleResp(_ dataGotten: Result<Response?, Error>, completion: (() -> Void)? = nil) { .... DispatchQueue.main.async { .... GlobalStaticStruct.code = 7 // 任务完成后触发回调 completion?() ... } }
之后测试里就可以直接把期望和回调绑定,不用依赖主队列的执行顺序啦。
内容的提问来源于stack exchange,提问作者Matias Barrios
相关产品推荐
相关产品推荐

