«1. Обзор

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

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

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

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

2. Тестирование RxJava — традиционный способ

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

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

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

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

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

List<String> letters = Arrays.asList("A", "B", "C", "D", "E");
List<String> results = new ArrayList<>();
Observable<String> 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"));

Мы собираем результаты от наблюдателя, добавляя элементы в список результатов. Наблюдатель и наблюдаемое работают в одном и том же потоке, поэтому наше утверждение правильно блокируется и ожидает завершения метода subscribe().

3. Тестирование RxJava с использованием TestSubscriber

RxJava поставляется с классом TestSubscriber, который позволяет нам писать тесты, работающие с асинхронной обработкой событий. Это обычный наблюдатель, который подписывается на наблюдаемое.

В тесте мы можем проверить состояние TestSubscriber и сделать утверждения об этом состоянии:

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

Observable<String> 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 имеет несколько очень полезных методов утверждения, которые мы будем использовать для проверки наших ожиданий. Подписчик должен получить 5 испущенных элементов наблюдателем, и мы утверждаем это, вызывая метод assertValueCount().

Мы можем проверить все события, которые получил подписчик, вызвав метод getOnNextEvents().

Вызов метода assertCompleted() проверяет, завершен ли поток, на который подписан наблюдатель. Метод assertNoErrors() утверждает, что при подписке на поток ошибок не было.

4. Тестирование ожидаемых исключений

Иногда в нашей обработке, когда наблюдаемое генерирует события или наблюдатель обрабатывает события, возникает ошибка. У TestSubscriber есть специальный метод для проверки состояния ошибки — метод assertError(), который принимает в качестве аргумента тип исключения: ) метод. Второй наблюдаемый генерирует исключение RuntimeException при генерации следующего события. Мы можем проверить тип этого исключения в TestSubsciber, вызвав метод assertError().

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

Observable<String> 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();

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

5. Тестирование Observable, основанного на времени

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

Мы можем определить Observable, основанный на времени, используя метод Observable.interval() и передать TimeUnit в качестве аргумента:

Наблюдаемый тик будет выдавать новое значение каждую секунду.

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

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

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

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

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

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

«

scheduler.advanceTimeBy(1, TimeUnit.SECONDS);

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

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

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