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

如何用RxTest测试ViewModel中重复购买请求的过滤逻辑

问题描述

我们正在为ViewModel编写单元测试,需求是:当ViewModel收到requestInAppPayment输入请求时,仅当实例属性isPurchasing为false时才会执行订阅(用于防止购买按钮重复点击)。想要测试「购买进行中点击购买按钮,请求被过滤」的场景,但测试结果不符合预期——期望仅触发一次成功事件,实际却触发了两次。

ViewModel代码

requestInAppPayment
    .filter { [weak self] _ in
        self?.isPurchasing == false
    }
    .subscribe(with: self, onNext: { owner, inAppPayment in
        owner.isPurchasing = true
        owner.showIndicator.onNext(())
        owner.requestInAppPurchaseUseCase.execute(productID: inAppPayment.productID)
    })
    .disposed(by: disposeBag)

requestInAppPurchaseUseCase.purchaseSuccess
    .do(onNext: { [weak self] _ in
        self?.isPurchasing = false
        self?.hideIndicator.onNext(())
    })

单元测试代码

let observer = scheduler.createObserver(String.self)
let stubPaymentItem = InAppPayment()

scheduler.createHotObservable([
    .next(10, stubPaymentItem),
    .next(20, stubPaymentItem)
])
.bind(to: viewModel.input.requestInAppPurchase)
.disposed(by: disposeBag)

viewModel.output.showPaymentResultPage
    .map { "success" }
    .drive(observer)
    .disposed(by: disposeBag)

scheduler.start()

// 实际结果: [.next(10, "success"), .next(20, "success")]
// 预期结果: [.next(10, "success")]

XCTAssertEqual(observer.events, [
    .next(10, "success")
])
问题原因

测试中未正确控制requestInAppPurchaseUseCase.purchaseSuccess的发送时机。默认情况下,execute方法可能同步触发了purchaseSuccess事件,导致第一次购买完成后立刻将isPurchasing设为false,此时第二次点击请求到达时,isPurchasing已经回到false,所以请求没有被过滤,最终触发了两次成功事件。

解决方案

需要模拟requestInAppPurchaseUseCase的行为,让purchaseSuccess事件在第二次点击请求之后再发送,确保第二次点击时isPurchasing仍为true,从而验证过滤逻辑。

修改后的单元测试代码示例:

let observer = scheduler.createObserver(String.self)
let stubPaymentItem = InAppPayment()
// 模拟UseCase的purchaseSuccess事件,在30时刻发送(晚于第二次点击的20时刻)
let mockPurchaseSuccess = scheduler.createHotObservable([
    .next(30, ())
])
// 将模拟的事件绑定到UseCase的purchaseSuccess上(假设该属性是可绑定的Subject)
mockPurchaseSuccess.bind(to: viewModel.requestInAppPurchaseUseCase.purchaseSuccess)
    .disposed(by: disposeBag)

scheduler.createHotObservable([
    .next(10, stubPaymentItem),
    .next(20, stubPaymentItem)
])
.bind(to: viewModel.input.requestInAppPurchase)
.disposed(by: disposeBag)

viewModel.output.showPaymentResultPage
    .map { "success" }
    .drive(observer)
    .disposed(by: disposeBag)

scheduler.start()

// 此时第二次点击(20时刻)时,isPurchasing仍为true,请求被过滤
XCTAssertEqual(observer.events, [
    .next(10, "success")
])

另外,也可以确保requestInAppPurchaseUseCase.execute的逻辑是异步的,或者在测试中控制其执行时机,避免purchaseSuccess同步触发导致isPurchasing提前重置。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 22:02:17