例えば、次のコードのような 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()
で元に戻しておくことを忘れないでください。