Как проверить RxJava?

Как проверить RxJava?

1. обзор

В этой статье мы рассмотрим способы тестирования кода, написанного с использованиемRxJava.

Типичный поток, который мы создаем с помощью RxJava, состоит изObservable иObserver.. Observable - это источник данных, который представляет собой последовательность элементов. Один или несколько наблюдателей подписываются на него, чтобы получать испущенные события.

Как правило, наблюдатель и наблюдаемые выполняются в отдельных потоках асинхронно, что затрудняет традиционное тестирование кода.

К счастью,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, получает письма, заархивированные целыми числами.

Написание такого теста традиционным способом означает, что нам нужно вести список результатов и обновлять этот список от наблюдателя. Добавление элементов в список целых чисел означает, что наши наблюдаемые и наблюдатели должны работать в одном потоке - они не могут работать асинхронно.

Таким образом, нам будет не хватать одного из самых больших преимуществ RxJava - обработки событий в отдельных потоках.

Вот как будет выглядеть эта ограниченная версия теста:

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. Тестирование RxJava с использованиемTestSubscriber

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"));

Мы передаем экземплярTestSubscriber методуsubscribe() наблюдаемого объекта. Затем мы можем изучить состояние этого абонента.

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(). Второй наблюдаемый бросаетRuntimeException при генерации следующего события. Мы можем проверить тип этого исключения наTestSubsciber, вызвав методassertError().

Наблюдатель, который получает ошибку, прекращает обработку и оказывается в незавершенном состоянии. Это состояние можно проверить методомassertNotCompleted().

5. Время тестированияObservableс

Допустим, у нас естьObservable, который генерирует одно событие в секунду, и мы хотим проверить это поведение с помощьюTestSubsciber.

Мы можем определить основанный на времениObservable, используя методObservable.interval(), и передать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 будет выдавать новое значение каждую секунду.

В начале теста мы находимся в нулевом времени, поэтому нашTestSubscriber не будет завершен:

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

Чтобы имитировать прохождение времени в нашем тесте, нам нужно использовать классTestScheduler. Мы можем смоделировать этот односекундный проход, вызвав методadvanceTimeBy() дляTestScheduler:

scheduler.advanceTimeBy(1, TimeUnit.SECONDS);

МетодadvanceTimeBy() заставит наблюдаемое произвести одно событие. Мы можем утверждать, что одно событие было вызвано вызовом методаassertValueCount():

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, поэтому его должно быть легко импортировать и запускать как есть.