提问人:Jiss 提问时间:6/27/2023 更新时间:6/27/2023 访问量:66
如何在 RxTest 中测试多个事件
How to test multiple events in RxTest
问:
我们目前正在为我们的 ViewModel 实现单元测试。
当 requestInAppPayment 输入请求来自视图模型时,仅当实例属性 isPurchasing 筛选器为 false 时,才会实现订阅(以防止多次触摸购买按钮)。
我想在购买过程中按下购买按钮时为过滤器编写一个测试用例,但我不知道该怎么做。
我想编写一个测试用例,用于在购买过程中单击购买按钮时进行筛选。
在 ViewMode 中
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()
// result = [.next(10, "success"), .next(20, "success")],
// The logic I think is [.next(10, "success")]
XCTAssertEqual(observer.events, [
.next(10, "success")
])
答:
0赞
Daniel T.
6/27/2023
#1
这是一个完全可行的示例,仅基于您提供的单元测试的少量内容。我根本没有使用你的视图模型代码,我只是写了一个通过测试的视图模型。
我使用了我在这里提供的测试工具:https://gist.github.com/danielt1263/bd449100764e3166644f7a38bca86c96
class ViewModelTests: XCTestCase {
func test() {
let scheduler = TestScheduler(initialClock: 0)
let disposeBag = DisposeBag()
let iapObserver = scheduler.createObserver(InAppPayment.self)
let mockUseCase = MockRIAP(scheduler: scheduler)
let viewModel = ViewModel(requestInAppPurchaseUseCase: mockUseCase)
// I had to add all of the above to make the test compile.
let observer = scheduler.createObserver(String.self)
let stubPaymentItem = InAppPayment(id: 25) // I had to give the object an ID
scheduler.createObservable(timeline: "-AA", values: ["A": stubPaymentItem]) // waits one second then emits two stubPaymentItems one second apart.
.bind(to: viewModel.input.requestInAppPurchase)
.disposed(by: disposeBag)
viewModel.output.showPaymentResultPage
.map { "success" }
.drive(observer)
.disposed(by: disposeBag)
scheduler.start()
XCTAssertEqual(observer.events, [
.next(2, "success") // success emits at the 2 second mark because the trigger waits a second and then the mock waits a second. The second trigger is ignored.
])
}
}
class MockRIAP: RequestInAppPurchaseUseCase {
let args: TestableObserver<InAppPayment.ID>
let _execute: (InAppPayment.ID) -> Observable<IAPResponse>
init(scheduler: TestScheduler) {
args = scheduler.createObserver(InAppPayment.ID.self)
_execute = scheduler.mock(args: args, values: ["A": IAPResponse()], timelineSelector: { _ in "-A|" }) // waits one second then emits an IAPResponse
}
func execute(id: InAppPayment.ID) -> RxSwift.Observable<IAPResponse> {
_execute(id)
}
}
以下是使上述工作正常工作的生产代码:
protocol RequestInAppPurchaseUseCase {
func execute(id: InAppPayment.ID) -> Observable<IAPResponse>
}
struct ViewModel {
struct Input {
let requestInAppPurchase: AnyObserver<InAppPayment>
}
struct Output {
let showPaymentResultPage: Driver<Void> // a Driver<Void> doesn't make sense. Why would you want to emit the previous result? Better would be a Signal<Void>.
}
let input: Input
let output: Output
}
extension ViewModel {
init(requestInAppPurchaseUseCase: RequestInAppPurchaseUseCase) {
let _requestInAppPurchase = PublishSubject<InAppPayment>()
let showResults = _requestInAppPurchase
.flatMapFirst { purchase in
requestInAppPurchaseUseCase.execute(id: purchase.id)
}
.map { _ in }
.asDriver(onErrorDriveWith: .empty())
input = Input(requestInAppPurchase: _requestInAppPurchase.asObserver())
output = Output(showPaymentResultPage: showResults)
}
}
struct InAppPayment: Identifiable {
let id: Int
}
struct IAPResponse { }
评论
showPaymentResultPage
InAppPayment
flatMapFirst