例えば、次のコードのような 5秒後に "foo" という String を emit する Observable をかえす関数があるとします。
fun observeFoo(): Observable<String> = Observable.just("foo").delay(5, TimeUnit.SECONDS)
この関数をユニットテストすることを考えます。
RxJava では Observable や Single などがどんな値を emit したかを検証する目的で TestObserver をつかってユニットテストを書きます。
Observable や Single などには、この TestObserver に変換する test() メソッドがあるので、このあとに 1つめの値は "foo" である ことを確かめるメソッドをチェインしていきます。
@Test fun observeFoo_getAfter5s() { observeFoo() .test() .assertValueAt(0) { value -> value == "foo" } }
このテストはそのままでは動きません。なぜなら 5秒後 ということを無視しており、すぐさま assertValueAt で値が"foo"かどうか検証してしまうからです。
TestObserver には、値が X 個流れてくるまで待つために awaitCount メソッドが用意してあります。この場合は5秒後に1個値が出るので、次のようにすると正しくテストが動きます。
@Test fun observeFoo_getAfter5s() { observeFoo() .test() .awaitCount(1) // 値が 1 個くるまで待つ .assertValueAt(0) { value -> value == "foo" } }
ただし、この方法では、とにかく値が1個くるまでスレッドを止めて待つため、ユニットテストの実行を5秒間止めてしまいます。 同様のテストケースが増えれば増えるほど、勢いよくテストが遅くなるのは明らかです。
delay は ComutationScheduler を使って時間をはかり、指定した時間が経過したら値を emit します。
RxJava ではいろいろな Scheduler をテスト用に上書きするための RxJavaPlugins があり、さらに時間を仮想的に操作するための TestScheduler も用意してあります。
これらを用いて次のように記述すると、5秒間スレッドを止めることなく即座に値の検証が可能になります。
@Test fun observeFoo_getAfter5s() { // delay は標準で computation scheduler を使うので、はじめに computation scheduler を test scheduler に上書きする val testScheduler = TestScheduler() RxJavaPlugins.setComputationSchedulerHandler { testScheduler } // TestObserver を得る val testObserver = observeFoo().test() // 仮想的に時間を 5 秒すすめる testScheduler.advanceTimeBy(5, TimeUnit.SECONDS) // "foo" が emit されているので検証ができる testObserver.assertValueAt(0) { value -> value == "foo" } }
最後に、RxJavaPlugins で上書きしたものは、@After などで RxJavaPlugins.reset() で元に戻しておくことを忘れないでください。