You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

异步函数中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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 14:18:05