LoginSignup
5
5

More than 5 years have passed since last update.

RxJava2では失敗してくれないObservableのテスト

Last updated at Posted at 2017-05-25

この記事について

この記事は、以下のような人に役立ちます。つまり、私のことです。
- RxJava2を使っている
- ObservableやFlowableを返すメソッドのテストを書きたい
- TestObserverやTestSubscriberを使っていなかった

今まで書いていたObservableのテストが失敗しなくなったので、原因と対策を調べてみました。結論としては、TestObserverやTestSubscriberを使いましょうという話です。

前提

  • RxJava 2.1.0 を利用
  • テスティングフレームワークは Junit 4.12
  • ラムダ式(Retrolambda)を利用

失敗してくれないテスト

まずは、興味深い例を紹介します。

@Test
public void test() throws Exception {
    Observable.just(1).subscribe(integer -> {
        Assert.assertEquals(2, (int) integer);
    });
}

このテストはRxJava1では失敗しますが、RxJava2では失敗しません。期待する値は2なのに対し、Observableから流れてくる値は1なので、assertEqualsメソッドでAssertionErrorが発生します。AssertionErroronNext()の処理中に発生するのでonError()の処理が呼ばれます。
ここで、RxJava1ではOnErrorNotImplementedExceptionが発生して、テストが失敗します。しかし、RxJava2ではOnErrorNotImplementedExceptionがコンソールに出力されるものの、テストは成功してしまいます。

以下のようにonError()でfailさせてもダメです。

@Test
public void test() throws Exception {
    Observable.just(1).subscribe(integer -> {
        Assert.assertEquals(2, (int) integer);
    }, throwable -> Assert.fail());
}

原因

これは、RxJava2のエラーハンドリング方針が変更されたためにおきる現象です。onErrorの中でThrowableが発生した場合、RxJava1ではthrowされますが、RxJava2ではRxJavaPlugins.onError(Throwable error)で処理されます。RxJavaPlugins.onError(Throwable error)は、(特に何も設定していない場合)エラーのスタックトレースを出力し、最終的にはUncaughtExceptionHandler#uncaughtExceptionにエラー処理を任せます。

RxJavaPlugins.java
/**
 * Called when an undeliverable error occurs.
 * @param error the error to report
 */
public static void onError(@NonNull Throwable error) {
    Consumer<? super Throwable> f = errorHandler;

    if (error == null) {
        error = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources.");
    } else {
        if (!isBug(error)) {
            error = new UndeliverableException(error);
        }
    }

    if (f != null) {
        try {
            f.accept(error);
            return;
        } catch (Throwable e) {
            // Exceptions.throwIfFatal(e); TODO decide
            e.printStackTrace(); // NOPMD
            uncaught(e);
        }
    }

    error.printStackTrace(); // NOPMD
    uncaught(error);
}

static void uncaught(@NonNull Throwable error) {
    Thread currentThread = Thread.currentThread();
    UncaughtExceptionHandler handler = currentThread.getUncaughtExceptionHandler();
    handler.uncaughtException(currentThread, error);
}

JUnitはテストメソッドが例外を投げた時にテストが失敗したと判断しますが、RxJava2ではThrowableUncaughtExceptionHandler#uncaughtExceptionで処理され、throwされることがないためにテストが失敗しないと考えられます。

対策

TestObserverやTestSubscriberを使うことで、この問題は解決します。今回の例では、以下のようにTestObserverを使うとテストが期待通りに失敗します。

@Test
public void test() throws Exception {
    Observable.just(1)
            .test() // TestObserverを返す
            .assertValue(2);
}

結論

  • RxJava2ではエラーハンドリングの方針が変わり、例外がthrowされなくなった
  • ObservableのテストにはTestObserverやTestSubscriberを使いましょう

subscribeの中でassert系のメソッド呼び出しをしている場合は、RxJava2でテストが失敗しなくなるので気をつけましょう。

参考記事

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5