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

SwiftUI中使用swift-snapshot-testing如何等待异步Task完成?

解决SwiftUI快照测试异步加载问题

问题1:异步配置Mock的替代实现方式

不需要额外创建Task来配置异步Mock,直接把测试方法改成异步函数即可。XCTest从Xcode 13开始支持异步测试方法,你可以直接在方法内同步配置异步Mock:

func testPrintersListViewSnapShot() async {
    let mockedPrinterResult: [PrinterModel] = [
        PrinterModel.moc(status: .maintenance, id: 1)
    ]
    
    // 直接在异步测试方法内配置Mock,无需额外Task
    given(await printerControllerMock.fetchPrinters()).willReturn(.success(mockedPrinterResult))
    
    let printersViewModel = PrintersViewModel(printerController: printerControllerMock, errorHandler: errorHandlerMock)
    let sut = PrintersListView(printersViewModel: printersViewModel)
    
    // 后续处理异步等待逻辑...
    assertSnapshot(of: sut, as: .image)
}

这样既避免了额外的Task调度,也让Mock配置逻辑更清晰。

问题2:等待异步任务完成后生成快照

要解决快照提前生成的问题,核心是等待ViewModel的异步数据加载完成,再触发快照。这里提供两种可靠方案:

方案1:利用ViewModel的状态监听(推荐)

如果你的PrintersViewModel有类似@Published var isLoading: Bool或者@Published var printers: [PrinterModel]?的状态属性,可以通过监听这些属性的变化,等待数据加载完成:

func testPrintersListViewSnapShot() async {
    let mockedPrinterResult: [PrinterModel] = [
        PrinterModel.moc(status: .maintenance, id: 1)
    ]
    
    given(await printerControllerMock.fetchPrinters()).willReturn(.success(mockedPrinterResult))
    
    let printersViewModel = PrintersViewModel(printerController: printerControllerMock, errorHandler: errorHandlerMock)
    let sut = PrintersListView(printersViewModel: printersViewModel)
    
    // 等待ViewModel的打印机数据加载完成
    await printersViewModel.$printers.values.first { $0 != nil }
    
    assertSnapshot(of: sut, as: .image)
}

注:需要确保ViewModel的@Published属性在数据加载完成时正确触发更新。

方案2:手动触发数据加载(跳过.task自动执行)

如果PrintersViewModel对外暴露了数据加载的方法(比如func fetchPrinters()),可以直接在测试里手动调用并等待完成,不用依赖视图的.task修饰符,这样更可控:

func testPrintersListViewSnapShot() async {
    let mockedPrinterResult: [PrinterModel] = [
        PrinterModel.moc(status: .maintenance, id: 1)
    ]
    
    given(await printerControllerMock.fetchPrinters()).willReturn(.success(mockedPrinterResult))
    
    let printersViewModel = PrintersViewModel(printerController: printerControllerMock, errorHandler: errorHandlerMock)
    // 手动触发并等待数据加载完成
    await printersViewModel.fetchPrinters()
    
    let sut = PrintersListView(printersViewModel: printersViewModel)
    assertSnapshot(of: sut, as: .image)
}

这种方式完全绕过了视图的异步生命周期,直接控制数据加载时机,测试稳定性更高。

备选方案:利用延迟等待视图更新

如果以上方案不适用,还可以用XCTest的期望等待视图完成重渲染(仅作为备选,稳定性略差):

func testPrintersListViewSnapShot() async {
    let mockedPrinterResult: [PrinterModel] = [
        PrinterModel.moc(status: .maintenance, id: 1)
    ]
    
    given(await printerControllerMock.fetchPrinters()).willReturn(.success(mockedPrinterResult))
    
    let printersViewModel = PrintersViewModel(printerController: printerControllerMock, errorHandler: errorHandlerMock)
    let sut = PrintersListView(printersViewModel: printersViewModel)
    
    // 创建期望,等待视图完成重渲染
    let expectation = XCTestExpectation(description: "View updated with data")
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
        expectation.fulfill()
    }
    await fulfillment(of: [expectation], timeout: 1)
    
    assertSnapshot(of: sut, as: .image)
}

内容的提问来源于stack exchange,提问作者esbenr

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 20:04:52