单元测试@Published变量:解决监听时底层值未同步问题
问题描述
我有如下ViewModel代码结构:
class ViewModel: ObservableObject { @Published var data: [Int] = [] func fetchData() { someAPICall() { result in data = result // 假设此处会插入10个元素 } } }
编写的单元测试代码如下:
func testViewModel() { let waitExpectation = expectation(description: "testVM") let viewModel = ViewModel() viewModel.$contentUnitViewModels .dropFirst() // 忽略VM初始化时的首次发布 .sink { data in waitExpectation.fulfill() XCTAssertEqual(viewModel.data.count, 10) // viewModel.data为空,但闭包中的data有预期的10个值 } .store(in: &cancellables) viewModel.fetchData() waitForExpectations(timeout: waitExpectationTimeout) { error in XCTAssertNil(error) } }
遇到的问题:在SwiftUI视图中使用@Published属性data时数据能正常渲染,但单元测试里viewModel.data为空(count为0),而sink闭包中传入的data却有10个值,看起来发布通知比底层数组实际设置更早,想知道如何修复,确保只有在数组真正设置后才收到通知。
解决方案
1. 修正测试中的订阅对象错误
你测试代码里订阅的是viewModel.$contentUnitViewModels,但ViewModel定义的@Published属性是data,这是明显笔误。把订阅对象改成viewModel.$data,才能正确监听data的变化:
viewModel.$data .dropFirst() .sink { receivedData in waitExpectation.fulfill() // 直接断言收到的数据符合预期 XCTAssertEqual(receivedData.count, 10) // 同时验证ViewModel的属性和收到的数据一致 XCTAssertEqual(receivedData, viewModel.data) } .store(in: &cancellables)
2. 确保在主线程更新@Published属性
@Published的通知默认会在属性设置的线程发送,如果someAPICall的回调是在后台线程,直接设置data可能导致属性更新和通知发布的时序问题。修正ViewModel的fetchData方法,确保在主线程更新data:
func fetchData() { someAPICall() { result in DispatchQueue.main.async { self.data = result } } }
ObservableObject的属性更新建议始终在主线程执行,这不仅能解决测试中的时序问题,也符合SwiftUI视图更新必须在主线程的要求。
3. 优化测试断言逻辑
sink闭包中收到的receivedData就是data更新后的最新值,直接断言这个值的属性即可,同时验证它和viewModel.data的一致性,确保两者状态同步。
经过以上调整,测试就能正确捕获data的更新,并且viewModel.data和闭包中收到的数据会保持一致,不会出现空值的情况。
内容的提问来源于stack exchange,提问作者Shawn Frank
相关产品推荐
相关产品推荐

