ViewModel测试中Success分支未触发@Published属性更新问题
依赖注入下Mock对象测试ViewModel的Success分支未触发问题解决
问题场景
刚接触依赖注入,测试PopularTVViewModel时,调用getShows()后,尽管Mock对象的返回结果已设为Success,但ViewModel中Result的Success分支未及时执行,导致@Published属性popularTV未更新,验证viewModel.popularTV.count == 4的断言失败。已确认Mock对象存在且ViewModel持有其引用,但问题仍存在。
核心原因
ViewModel的getShows()方法中,completion回调内的逻辑被包裹在DispatchQueue.main.async中,而测试代码是同步执行的——断言在异步代码完成前就已执行,此时popularTV还未完成更新。
解决方案
使用XCTestExpectation等待异步任务执行完成,确保断言在popularTV更新后再触发。同时可优化MockManager的计数器调用顺序(非必须,但更符合逻辑)。
修改后的测试代码
import XCTest @testable import PopularTVViewer class PopularTVViewerTests: XCTestCase { func testPopularTVModel() { let mockedManager = MockedPopularTVManager() mockedManager.result = .success(mockedManager.mockPopularShows) let viewModel = PopularTVViewModel(manager: mockedManager) // 创建期望,等待异步任务完成 let loadExpectation = self.expectation(description: "Popular TV shows loaded") // 监听@Published属性变化,满足条件时触发期望 let cancellable = viewModel.$popularTV.sink { shows in if shows.count == 4 { loadExpectation.fulfill() } } viewModel.getShows() XCTAssertNotNil(viewModel) XCTAssertNotNil(mockedManager.result) XCTAssertEqual(mockedManager.getPopularShowsCallCounter, 1) // 等待期望完成,超时时间设为1秒足够测试场景 waitForExpectations(timeout: 1) // 异步任务完成后再做最终断言 XCTAssertEqual(viewModel.popularTV.count, 4) cancellable.cancel() } }
可选优化:调整MockManager的计数器顺序
将计数器递增放在completion调用之前,更符合"标记调用→返回结果"的逻辑:
func getPopularShows(completion: @escaping (Result<[PopularTV], NetworkError>) -> Void) { getPopularShowsCallCounter += 1 completion(result) }
补充说明
XCTestExpectation是处理异步测试的标准方式,能确保测试代码等待异步逻辑执行完毕再验证结果。- 监听
@Published属性变化是触发期望最可靠的方式,避免依赖固定等待时间带来的不稳定。
内容的提问来源于stack exchange,提问作者tylerperkins
相关产品推荐
相关产品推荐

