RxTestを使ってみる
さっと動かしてみる
func test_observer() { // ①TestSchedulerを生成する (仮想時刻を0で初期化) let scheduler = TestScheduler(initialClock: 0) // ②TestObserverを生成する let observer = scheduler.createObserver(Int.self) // ③イベントを流す scheduler.scheduleAt(10) { observer.onNext(1) } scheduler.scheduleAt(20) { observer.onNext(2) } scheduler.scheduleAt(30) { observer.onNext(3) } scheduler.scheduleAt(40) { observer.onCompleted() } // ④TestSchedulerをスタートさせる scheduler.start() XCTAssertEqual(observer.events, [ next(10, 1), next(20, 2), next(30, 3), completed(40) ]) }
TestScheduler
最小限以下で大丈夫そう
// 仮想時刻を指定して初期化 let scheduler = TestScheduler(initialClock: 0) // 任意の型のObserverを生成 let observer = scheduler.createObserver(Int.self) // Hot or Coldのobservableを生成 scheduler.createColdObservable([ next(10, 1), next(20, 2), completed(30) ]) // 任意の時刻にクロージャーの処理を実行 scheduler.scheduleAt(200) { } // schedulerを開始 scheduler.start()
HotObservableをテストしてみる
func test_hot_observable() { let bag = DisposeBag() // ①TestSchedulerを生成する (仮想時刻を0で初期化) let scheduler = TestScheduler(initialClock: 0) // ②TestObservableを生成する let observable: TestableObservable<Int> = scheduler.createHotObservable([ next(100, 1), next(200, 2), next(300, 3), next(400, 4), completed(500) ]) // ③TestObserverを生成する let observer: TestableObserver<String> = scheduler.createObserver(String.self) // ④TestObserverをTestObservableに購読させる (仮想時刻250にクロージャーの処理を実行) scheduler.scheduleAt(250) { observable .map { String($0) } .subscribe(observer) .disposed(by: bag) } // ⑤TestSchedulerをスタートさせる scheduler.start() // ⑥イベントの検証 (今回はHotなObservableに仮想時刻250で購読したのでそれ以前のイベントは流れてこない) let expectedEvents = [ next(300, "3"), next(400, "4"), completed(500) ] XCTAssertEqual(observer.events, expectedEvents) }
こちらを最新版で動かした。あと若干コメントが間違ってた。
シンプルなViewModelをテストしてみる
動画プレイヤーのようにボタンをタップすると再生・ポーズが切り替わるような画面を想定する。
func test_toggle_playing() { let bag = DisposeBag() let scheduler = TestScheduler(initialClock: 0) // --tap--tap--tap--tap-- let tapButtonEvent = scheduler.createHotObservable([ next(10, ()), next(20, ()), next(30, ()), next(40, ()) ]).asObservable() // observer let isPlaying = scheduler.createObserver(Bool.self) let viewModel = TestViewModel() tapButtonEvent .bind(to: viewModel.input.tapPlayButton) .disposed(by: bag) viewModel .output .isPlaying .bind(to: isPlaying) .disposed(by: bag) scheduler.start() XCTAssertEqual(isPlaying.events, [ next(0, false), // 初期値 next(10, true), next(20, false), next(30, true), next(40, false), ]) }
ボタンタップの入力ストリームと再生中かを表す出力がいい感じにテストできた。
ボタンタップのイベントをObservable
に抽象化?したので実際にボタンを叩かなくても、イベントを作成すれば良い。 - またViewModelの出力をうまくViewとbindできていることを前提とすれば、outputをテストできればUITestは省略できそう。
少し大げさだがこの時のViewModel
final class TestViewModel { struct Output { var isPlaying: Observable<Bool> } struct Input { var tapPlayButton: AnyObserver<Void> } private let bag = DisposeBag() private let isPlaying = BehaviorRelay(value: false) let input: Input let output: Output init() { let _tapPlayButton = PublishSubject<Void>() input = Input( tapPlayButton: _tapPlayButton.asObserver() ) output = Output( isPlaying: isPlaying.asObservable() ) _tapPlayButton.subscribe(onNext: { [unowned self] in self.isPlaying.accept(!self.isPlaying.value) }).disposed(by: bag) } }