You need to enable JavaScript to run this app. SwiftUI中如何为ObservableObject ViewModel的@Published变量编写单元测试
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中如何为ObservableObject ViewModel的@Published变量编写单元测试

基于你提供的代码,可行的单元测试实现思路如下

第一步:先修复ViewModel的隐藏bug

你当前的getList方法存在逻辑错误:已经通过services.resolve(ListService.self)拿到了注入的服务实例,却直接调用ListService()新建了实例,导致依赖注入完全失效,测试时无法注入Mock服务。修复后的代码如下:

func getList() {
    if let listService = self.services.resolve(ListService.self) {
        // 用注入的listService实例,不要新建
        listService.getList() { [weak self] result in
            switch result {
                case .success(let dataRecieved):
                    DispatchQueue.main.async {
                        self?.list = dataRecieved
                    }
                case .failure(let error):
                    print(error)
            }
        }
    }
}

第二步:编写Mock ListService用于测试

单元测试需要避免依赖真实网络请求,需要构建Mock服务来模拟返回结果,在测试Target下新增如下实现:

class MockListService: ListService {
    var mockResult: Result<List, Error>!
    override func getList(completion: @escaping (Result<List, Error>) -> Void) {
        completion(mockResult)
    }
}

第三步:编写单元测试用例

需要注意Combine的sink订阅必须持有cancellable,否则订阅会立即释放,无法收到值回调,这也是你当前测试大概率失败的核心原因:

import XCTest
import Combine
@testable import 你的App模块名

class ListViewModelTest: XCTestCase {
    // 持有所有订阅,避免提前释放
    var cancellables = Set<AnyCancellable>()
    
    func testGetListSuccess() {
        // 1. 准备Mock数据和依赖
        let mockServices = Services()
        let mockList = List(items: [ListItem()]) // 按你的List结构构造测试数据
        let mockService = MockListService()
        mockService.mockResult = .success(mockList)
        mockServices.register(mockService, for: ListService.self)
        
        // 2. 定义测试期望
        let actionExpectation = XCTestExpectation(description: "列表数据加载完成")
        
        // 3. 初始化ViewModel并订阅属性变化
        let listViewModel = ListViewModel(services: mockServices)
        listViewModel.$list
            .compactMap { $0 } // 过滤初始nil值,避免第一次空值触发断言
            .sink(receiveValue: { list in
                XCTAssertEqual(list.items.count, 1)
                actionExpectation.fulfill()
            })
            .store(in: &cancellables)
        
        // 4. 等待期望完成
        wait(for: [actionExpectation], timeout: 1)
    }
}

可选优化:加速测试执行

如果要跳过DispatchQueue.main.async的等待耗时,可以在测试环境下注入自定义队列替代主队列,或者调用XCTestCase扩展的waitForMainQueue()方法处理主队列待执行任务,不需要设置超时等待。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.22 06:36:07