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

RxSwift PublishSubject单元测试订阅回调不触发问题求解

问题根因

你使用的PublishSubject属于热序列,特性是只会向订阅完成之后的订阅者发送新产生的事件,现有代码的执行顺序存在时序问题:

  1. 先调用viewModel.getCities(),如果你的Mock服务是同步回调,会立刻触发cities.onNext发送数据
  2. 之后才执行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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 20:15:01