この記事について
この記事は、以下のような人に役立ちます。つまり、私のことです。
- 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
が発生します。AssertionError
はonNext()
の処理中に発生するので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
にエラー処理を任せます。
/**
* 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ではThrowable
がUncaughtExceptionHandler#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でテストが失敗しなくなるので気をつけましょう。