如何在Swift异步函数中测试await之前的代码?
Swift并发中测试加载状态的解决方法
你的测试用例之所以失败,核心原因是:await sut.getUsers()会等待整个getUsers()函数执行完毕才继续后续代码。而getUsers()里先设置viewState = .loading,紧接着就执行await apiClient.getUsers(),等这个异步请求完成后,viewState会被更新为.normal或.error,所以当你执行断言时,状态已经不是loading了。
下面是几种可行的解决方式:
1. 让模拟API客户端挂起,给测试留断言时间
修改你的MockAPIClient,让它的getUsers()方法可以挂起一段时间,确保测试能在状态更新前完成断言:
class MockAPIClient: APIClientProtocol { var shouldDelay = false var response: Result<Users, APIError> = .failure(.unknown) func getUsers() async -> Result<Users, APIError> { if shouldDelay { // 挂起100毫秒,足够测试完成断言 try? await Task.sleep(nanoseconds: 100_000_000) } return response } }
2. 并发执行getUsers和断言操作
不要直接await sut.getUsers(),而是把它放到后台任务中执行,同时立刻检查viewState:
func testShouldShowLoadingViewWhileLoadingUsers() async { mocAPIClient.shouldDelay = true mocAPIClient.response = Users.stub // 在后台任务调用getUsers,不等待它完成 Task { @MainActor in await sut.getUsers() } // 立刻断言加载状态 XCTAssertEqual(sut.viewState, .loading) }
3. 用XCTestExpectation监听状态变化(更严谨)
如果你的viewState是@Published属性,可以用监听的方式等待loading状态出现,避免依赖固定的延迟时间:
func testShouldShowLoadingViewWhileLoadingUsers() async { let loadingExpectation = XCTestExpectation(description: "View state updates to loading") mocAPIClient.shouldDelay = true mocAPIClient.response = Users.stub // 监听viewState的变化 let cancellable = sut.$viewState.sink { state in if state == .loading { loadingExpectation.fulfill() } } // 启动getUsers任务 Task { @MainActor in await sut.getUsers() } // 等待状态变为loading,超时时间1秒 await fulfillment(of: [loadingExpectation], timeout: 1.0) XCTAssertEqual(sut.viewState, .loading) // 取消监听 cancellable.cancel() }
内容的提问来源于stack exchange,提问作者Rahul
相关产品推荐
相关产品推荐

