异步函数中defer语句单元测试:如何验证isLoading被正确设为true
Swift defer语句导致isLoading状态单元测试失败的解决方案
首先明确:Swift的defer是语言级别的特性,会在函数退出前必然执行,没有办法直接在单元测试中强制跳过defer的执行,你的测试失败本质是检测isLoading的时机不对,而非defer逻辑有问题。你现在的测试中await了整个请求方法执行完成,此时defer已经完成赋值,自然读到的是false。
正确的解决思路是控制异步请求的完成时机,在请求还未结束、defer还未触发的时间点检测isLoading的状态,具体实现步骤如下:
步骤1:解耦网络请求依赖,方便测试注入Mock
首先把网络请求抽象为协议,允许测试时注入自定义的Mock实现,控制请求的执行节奏:
// 定义网络服务协议 protocol UserService { func getUser() async -> UserData } // 生产环境真实服务实现 struct RealUserService: UserService { func getUser() async -> UserData { await serverSideRequest.getUser() } } // 调整原业务类,支持依赖注入 class YourBusinessClass { var isLoading = false private let userService: UserService // 默认使用真实服务,测试时可传入Mock init(userService: UserService = RealUserService()) { self.userService = userService } func triggerServerSideRequest() async -> UserData { isLoading = true defer { isLoading = false } return await userService.getUser() } }
步骤2:实现Mock服务,控制请求执行时机
测试用的Mock服务可以通过XCTestExpectation卡住请求返回时机,让我们有窗口检测isLoading的中间状态:
class MockUserService: UserService { // 通知测试侧:请求已经发起,进入待返回状态 let requestDidStartExpectation = XCTestExpectation(description: "请求已发起") // 控制Mock什么时候返回结果 let requestCanFinishExpectation = XCTestExpectation(description: "请求允许结束") func getUser() async -> UserData { requestDidStartExpectation.fulfill() // 卡在这里等待测试侧指令,不返回结果 await fulfillment(of: [requestCanFinishExpectation], timeout: 1) return UserData() // 模拟返回假数据 } }
步骤3:调整单元测试逻辑
func testServerSideRequest_requestIsTriggered_isLoadingIsTrue() async { let mockService = MockUserService() let sut = YourBusinessClass(userService: mockService) // 启动请求任务,不要await,避免等待整个请求完成 Task { _ = await sut.triggerServerSideRequest() } // 等待请求已经发起的信号 await fulfillment(of: [mockService.requestDidStartExpectation], timeout: 1) // 此时defer还未触发,isLoading为true XCTAssertTrue(sut.isLoading) // 通知Mock可以结束请求,清理异步任务 mockService.requestCanFinishExpectation.fulfill() }
其他说明
- 如果不想调整现有代码的依赖结构,也可以用并发队列的优先级调度在请求执行间隙检测状态,但这类时序测试稳定性差,不推荐使用
- defer的设计目的就是保证清理逻辑必然执行,哪怕请求过程中抛出错误也会触发,不要尝试绕过defer执行,否则会破坏原有代码的可靠性保证
内容的提问来源于stack exchange,提问作者Roland Lariotte
相关产品推荐
相关产品推荐

