LoginSignup
13
12

More than 5 years have passed since last update.

先日、RxAnimationというAndroidのアニメーションのイベントをObservableでラッピングするライブラリを作成しました。
その中で、Rx系のライブラリのテストの書き方に困った時、同じくAndroidのViewをバインディングしているJakeのRxBindingのテストの書き方が参考になったので書きたいと思います。

主に、Rxのテストをするときチェックすのは想定通りのイベントがちゃんと流れてきているかです。
ということは、ストリームから流れてきたイベントを持っておく保持してチェックできれば良さそうですね。
ということで、参考にしたRxBindingのテストを踏まえて簡単に見ていきましょう。

使用しているライブラリ

テストに使用するライブラリは、基本的にAndroid Testing Support Library
具体的にはここらへん

具体的に参考にしたテスト

実際に参考にしたテストはこちら。
RecordingObserverというクラスが、ストリームから受け取ったイベントを保存してくれているみたいです。

RxViewTest.java
@Test
@UiThreadTest
public void clicks() {
    RecordingObserver<Void> o = new RecordingObserver<>();
    Subscription subscription = RxView.clicks(view).subscribe(o);
    o.assertNoMoreEvents(); // No initial value.

    view.performClick();
    assertThat(o.takeNext()).isNull();

    view.performClick();
    assertThat(o.takeNext()).isNull();

    subscription.unsubscribe();

    view.performClick();
    o.assertNoMoreEvents();
}

RxViewのclicksメソッドは、値としてViewClickOnSubscribeを返すようになっていて、ViewClickOnSubscribeの中身はこのような形でViewがクリックされると、onNextを呼ぶという代物です。

RecordingObserver

今回参考にしたRecordingObserverは、RxBindingのリポジトリの中にtesting-utilsというmoduleとして入っています。
RecordingObserverの中身はこのような形で、

RecordingObserver.java
public final class RecordingObserver<T> implements Observer<T> {
  private static final String TAG = "RecordingObserver";

  private final BlockingDeque<Object> events = new LinkedBlockingDeque<>();

  @Override public void onCompleted() {
    Log.v(TAG, "onCompleted");
    events.addLast(new OnCompleted());
  }

  @Override public void onError(Throwable e) {
    Log.v(TAG, "onError", e);
    events.addLast(new OnError(e));
  }

  @Override public void onNext(T t) {
    Log.v(TAG, "onNext " + t);
    events.addLast(new OnNext(t));
  }

  private <E> E takeEvent(Class<E> wanted) {
    Object event;
    try {
      event = events.pollFirst(1, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
    if (event == null) {
      throw new NoSuchElementException(
          "No event found while waiting for " + wanted.getSimpleName());
    }
    assertThat(event).isInstanceOf(wanted);
    return wanted.cast(event);
  }

  public T takeNext() {
    OnNext event = takeEvent(OnNext.class);
    return event.value;
  }

  public Throwable takeError() {
    return takeEvent(OnError.class).throwable;
  }

  public void assertOnCompleted() {
    takeEvent(OnCompleted.class);
  }

  public void assertNoMoreEvents() {
    try {
      Object event = takeEvent(Object.class);
      throw new IllegalStateException("Expected no more events but got " + event);
    } catch (NoSuchElementException ignored) {
    }
  }

  private final class OnNext {
    final T value;

    private OnNext(T value) {
      this.value = value;
    }

    @Override public String toString() {
      return "OnNext[" + value + "]";
    }
  }

  private final class OnCompleted {
    @Override public String toString() {
      return "OnCompleted";
    }
  }

  private final class OnError {
    private final Throwable throwable;

    private OnError(Throwable throwable) {
      this.throwable = throwable;
    }

    @Override public String toString() {
      return "OnError[" + throwable + "]";
    }
  }
}

各イベントクラス

  • OnNext.class
  • OnError.class
  • OnComplete.class

で受け取ったものを、Dequeにためています。
そのため、onNextが呼ばれたかをチェックするには、

RecordingObserver.java
assertThat(o.takeNext()).isNull();

onErrorが呼ばれたかをチェックするには、

RecordingObserver.java
assertThat(o.takeError()).isNull();

onCompleteが呼ばれたかをチェックするには、

RecordingObserver.java
o.assertOnCompleted();

これ以上イベントがDequeに溜まっていないかをチェックするには、

RecordingObserver.java
o. assertNoMoreEvents();

を実行すればよいみたいです。

おわりに

あまり身のない内容だったかもしれないですが、Rxのテストは戸惑いが多かったので、頭の整理も含め書いてみました。
Rxもテストもまだまだ初心者ですので、何かもっとよい知見があれば教えていただきたいです!

13
12
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
13
12