RxSwift PublishSubject单元测试订阅回调不触发问题求解
问题根因
你使用的PublishSubject属于热序列,特性是只会向订阅完成之后的订阅者发送新产生的事件,现有代码的执行顺序存在时序问题:
- 先调用
viewModel.getCities(),如果你的Mock服务是同步回调,会立刻触发cities.onNext发送数据 - 之后才执行
viewModel.cities.subscribe完成订阅,此时之前发出的事件已经丢失,自然不会触发订阅闭包
哪怕Mock服务是异步回调,现有写法也存在时序风险,无法稳定复现预期结果。
修复要点
- 调整执行顺序:先完成所有Subject的订阅逻辑,再调用
getCities()触发请求,保证事件发出时已有订阅者接收 - 新增
errorMessage订阅验证,满足无错误返回的校验需求 - 把
DisposeBag声明为测试类的全局属性,避免局部作用域释放导致订阅提前失效
修改后的完整测试代码
// 测试类全局持有DisposeBag,避免订阅提前释放 private let disposeBag = DisposeBag() func testSuccessfulResult() { let successExpectation = self.expectation(description: "成功收到城市列表") let noErrorExpectation = self.expectation(description: "成功场景无错误返回") // 配置反向期望:错误事件永远不触发才符合预期 noErrorExpectation.isInverted = true let service = MockCityServiceWithSuccessResponse() let viewModel = CitiesViewModel(service: service) // 先订阅城市列表 Subject viewModel.cities.subscribe { event in guard let cityList = event.element else { XCTFail("城市列表数据为空") return } XCTAssertGreaterThan(cityList.count, 0, "城市列表加载成功") successExpectation.fulfill() }.disposed(by: disposeBag) // 订阅错误 Subject,验证无错误返回 viewModel.errorMessage.subscribe { event in guard let errorMsg = event.element else { return } XCTFail("成功场景不应返回错误:\(errorMsg)") noErrorExpectation.fulfill() }.disposed(by: disposeBag) // 所有订阅完成后再触发请求 viewModel.getCities() wait(for: [successExpectation, noErrorExpectation], timeout: 2) }
如果你的项目引入了RxTest库,还可以使用TestObserver、TestScheduler做更精准的时序测试,无需依赖超时等待,执行效率更高。
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

