Swift如何测试封装在方法内的异步调用并等待执行完成
问题核心原因
断言失败的本质非常直接:refresh()内部创建的是脱离调用上下文的独立Task,调用refresh()后方法会立刻返回,不会等待内部的异步赋值逻辑执行完成。测试运行到断言行时,fetchData()的赋值步骤还没执行,data自然还是初始值nil。
可行解决方案
方案1:将方法标记为async(最优方案)
这是最符合Swift并发设计规范的做法,没有多余的等待逻辑,也不会出现测试结果不稳定的问题。
直接去掉refresh()内部多余的Task包装,把方法声明为异步:
class Owner{ let dataManager = MockDataManager() var data: String? = nil func refresh() async { self.data = await dataManager.fetchData() } }
测试用例直接使用XCTest原生的异步测试能力,等待方法执行完成再断言:
func testRefresh() async { await owner.refresh() XCTAssertEqual(owner.data, "test") }
如果生产场景必须保留refresh()同步调用、发后即忘的语义,只需要新增一个异步重载即可,生产调用方不用改任何代码,测试和内部逻辑走异步入口:
// 保留原有同步接口,供现有生产代码调用 func refresh() { Task { await refreshAsync() } } // 新增异步接口,供测试和需要等待结果的场景调用 func refreshAsync() async { self.data = await dataManager.fetchData() }
方案2:暴露内部Task句柄供测试等待
如果不能修改refresh()的对外签名,可以把内部创建的Task存为属性,测试场景下等待Task执行完成即可。
class Owner{ let dataManager = MockDataManager() var data: String? = nil // 访问权限设为internal,测试target通过@testable import即可访问,不会泄露给外部模块 var refreshTask: Task<Void, Never>? func refresh() { refreshTask = Task { self.data = await dataManager.fetchData() } } }
测试代码中直接等待Task的返回值:
func testRefresh() async { owner.refresh() await owner.refreshTask?.value XCTAssertEqual(owner.data, "test") }
方案3:XCTestExpectation轮询等待(兜底方案)
如果完全没有权限修改生产代码,可以用XCTest的期望值机制轮询检查data的值,直到符合预期再触发断言。
func testRefresh() { let loadExpect = expectation(description: "等待数据加载完成") // 短间隔轮询属性值,符合预期就结束等待 let monitorTimer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { timer in if owner.data == "test" { loadExpect.fulfill() timer.invalidate() } } owner.refresh() wait(for: [loadExpect], timeout: 1.0) monitorTimer.invalidate() XCTAssertEqual(owner.data, "test") }
注意:绝对不要用
Thread.sleep(forTimeInterval:)这类固定时长等待的写法,既会拖慢测试执行速度,机器负载高时还会随机出现测试失败。该方案稳定性远低于前两种,仅作为无法修改生产代码时的兜底选择。
内容的提问来源于stack exchange,提问作者George
相关产品推荐
相关产品推荐

