RxJavaをテストするには?

RxJavaをテストするには?

1. 概要

この記事では、RxJavaを使用して記述されたコードをテストする方法を見ていきます。

RxJavaで作成している一般的なフローは、ObservableObserver.で構成されています。オブザーバブルは、要素のシーケンスであるデータのソースです。 1人以上のオブザーバーがサブスクライブして、発行されたイベントを受信します。

通常、オブザーバーとオブザーバブルは非同期の方法で別々のスレッドで実行されます。これにより、従来の方法でコードをテストするのが難しくなります。

幸い、RxJava provides a TestSubscriber class which gives us the ability to test asynchronous, event-driven flow.

2. RxJavaのテスト–従来の方法

Let’s start with an example –1からの整数のシーケンスでzipする文字のシーケンスがあります。

テストでは、zipされたobservableによって発行されたイベントをリッスンするサブスクライバーが、整数で圧縮された文字を受け取ることをアサートする必要があります。

このようなテストを従来の方法で記述することは、結果のリストを保持し、オブザーバーからそのリストを更新する必要があることを意味します。 整数のリストに要素を追加することは、オブザーバブルとオブザーバーが同じスレッドで動作する必要があることを意味します。非同期で動作することはできません。

そのため、RxJavaの最大の利点の1つである、別々のスレッドでのイベントの処理が失われます。

限定バージョンのテストは次のようになります。

List letters = Arrays.asList("A", "B", "C", "D", "E");
List results = new ArrayList<>();
Observable observable = Observable
  .from(letters)
  .zipWith(
     Observable.range(1, Integer.MAX_VALUE),
     (string, index) -> index + "-" + string);

observable.subscribe(results::add);

assertThat(results, notNullValue());
assertThat(results, hasSize(5));
assertThat(results, hasItems("1-A", "2-B", "3-C", "4-D", "5-E"));

resultsリストに要素を追加することにより、オブザーバーからの結果を集約しています。 オブザーバーとオブザーバブルは同じスレッドで動作するため、アサーションは適切にブロックされ、subscribe()メソッドが終了するのを待ちます。

3. TestSubscriberを使用したRxJavaのテスト

RxJavaにはTestSubsriberクラスが付属しており、イベントの非同期処理で機能するテストを作成できます。 これは、オブザーバブルにサブスクライブする通常のオブザーバーです。

テストでは、TestSubscriberの状態を調べて、その状態についてアサーションを作成できます。

List letters = Arrays.asList("A", "B", "C", "D", "E");
TestSubscriber subscriber = new TestSubscriber<>();

Observable observable = Observable
  .from(letters)
  .zipWith(
    Observable.range(1, Integer.MAX_VALUE),
    ((string, index) -> index + "-" + string));

observable.subscribe(subscriber);

subscriber.assertCompleted();
subscriber.assertNoErrors();
subscriber.assertValueCount(5);
assertThat(
  subscriber.getOnNextEvents(),
  hasItems("1-A", "2-B", "3-C", "4-D", "5-E"));

オブザーバブルのsubscribe()メソッドにTestSubscriberインスタンスを渡します。 次に、このサブスクライバーの状態を調べることができます。

期待を検証するために使用するTestSubscriber has some very useful assertion methods。 サブスクライバーは、オブザーバーによって5つの放出された要素を受け取る必要があり、assertValueCount()メソッドを呼び出すことによってそれを表明します。

getOnNextEvents()メソッドを呼び出すことにより、サブスクライバーが受信したすべてのイベントを調べることができます。

assertCompleted()メソッドを呼び出すと、オブザーバーがサブスクライブしているストリームが完了しているかどうかがチェックされます。 assertNoErrors()メソッドは、ストリームのサブスクライブ中にエラーがなかったことを表明します。

4. 予想される例外のテスト

処理中に、オブザーバブルがイベントを発行しているとき、またはオブザーバーがイベントを処理しているときに、エラーが発生することがあります。 TestSubscriberには、エラー状態を調べるための特別なメソッドがあります。これは、例外のタイプを引数として取るassertError()メソッドです。

List letters = Arrays.asList("A", "B", "C", "D", "E");
TestSubscriber subscriber = new TestSubscriber<>();

Observable observable = Observable
  .from(letters)
  .zipWith(Observable.range(1, Integer.MAX_VALUE), ((string, index) -> index + "-" + string))
  .concatWith(Observable.error(new RuntimeException("error in Observable")));

observable.subscribe(subscriber);

subscriber.assertError(RuntimeException.class);
subscriber.assertNotCompleted();

concatWith()メソッドを使用して、別のオブザーバブルと結合されるオブザーバブルを作成しています。 2番目のオブザーバブルは、次のイベントを発行するときにRuntimeExceptionをスローします。 assertError()メソッドを呼び出すことにより、TestSubsciberでその例外のタイプを調べることができます。

エラーを受け取ったオブザーバーは処理を停止し、未完了状態になります。 その状態は、assertNotCompleted()メソッドで確認できます。

5. 時間ベースのObservableのテスト

1秒あたり1つのイベントを発行するObservableがあり、その動作をTestSubsciberでテストするとします。

Observable.interval()メソッドを使用して時間ベースのObservableを定義し、引数としてTimeUnitを渡すことができます。

List letters = Arrays.asList("A", "B", "C", "D", "E");
TestScheduler scheduler = new TestScheduler();
TestSubscriber subscriber = new TestSubscriber<>();
Observable tick = Observable.interval(1, TimeUnit.SECONDS, scheduler);

Observable observable = Observable.from(letters)
  .zipWith(tick, (string, index) -> index + "-" + string);

observable.subscribeOn(scheduler)
  .subscribe(subscriber);

tick observableは、1秒ごとに新しい値を発行します。

テストの開始時は時間ゼロであるため、TestSubscriberは完了しません。

subscriber.assertNoValues();
subscriber.assertNotCompleted();

テストで経過する時間をエミュレートするには、TestSchedulerクラスを使用する必要があります。 TestScheduleradvanceTimeBy()メソッドを呼び出すことにより、その1秒のパスをシミュレートできます。

scheduler.advanceTimeBy(1, TimeUnit.SECONDS);

advanceTimeBy()メソッドは、observableが1つのイベントを生成するようにします。 assertValueCount()メソッドを呼び出すことにより、1つのイベントが生成されたと断言できます。

subscriber.assertNoErrors();
subscriber.assertValueCount(1);
subscriber.assertValues("0-A");

lettersのリストには5つの要素が含まれているため、オブザーバブルですべてのイベントを発行する場合は、6秒の処理が必要です。 その6秒をエミュレートするには、advanceTimeTo()メソッドを使用します。

scheduler.advanceTimeTo(6, TimeUnit.SECONDS);

subscriber.assertCompleted();
subscriber.assertNoErrors();
subscriber.assertValueCount(5);
assertThat(subscriber.getOnNextEvents(), hasItems("0-A", "1-B", "2-C", "3-D", "4-E"));

経過時間をエミュレートした後、TestSubscriberでアサーションを実行できます。 assertValueCount()メソッドを呼び出すことにより、すべてのイベントが生成されたと断言できます。

6. 結論

この記事では、RxJavaでオブザーバーとオブザーバブルをテストする方法を検討しました。 放出されたイベント、エラー、時間ベースのオブザーバブルをテストする方法を検討しました。

これらすべての例とコードスニペットの実装は、GitHub projectにあります。これはMavenプロジェクトであるため、そのままインポートして実行するのは簡単です。