Como testar o RxJava?

Como testar o RxJava?

*1. Visão geral *

Neste artigo, veremos maneiras de testar o código escrito usando RxJava.

O fluxo típico que estamos criando com o RxJava consiste em um Observable e um Observer. O observável é uma fonte de dados que é uma sequência de elementos. Um ou mais observadores se inscrevem para receber eventos emitidos.

Normalmente, o observador e os observáveis ​​são executados em threads separados de forma assíncrona - o que dificulta o teste de código da maneira tradicional.

Felizmente,* RxJava fornece uma classe _http://reactivex.io/RxJava/javadoc/rx/observers/TestSubscriber.html [TestSubscriber] _ que nos permite a capacidade de testar fluxo assíncrono e controlado por eventos. *

===* 2. Testando o RxJava - a maneira tradicional *

*Vamos começar com um exemplo* - temos uma sequência de letras que queremos compactar com uma sequência de números inteiros de 1 inclusive.

Nosso teste deve afirmar que um assinante que ouve eventos emitidos por observáveis ​​compactados recebe letras compactadas com números inteiros.

Escrever esse teste de maneira tradicional significa que precisamos manter uma lista de resultados e atualizá-la de um observador. Adicionar elementos a uma lista de números inteiros significa que nossos observáveis ​​e observadores precisam trabalhar no mesmo encadeamento - eles não podem trabalhar de forma assíncrona.

*E assim estaríamos perdendo uma das maiores vantagens do RxJava - processamento de eventos em threads separados.*

Aqui está a aparência dessa versão limitada do teste:

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

Agregamos resultados de um observador adicionando elementos a uma lista de resultados. O observador e o observável trabalham no mesmo encadeamento, para que nossa asserção bloqueie corretamente e aguarde a conclusão de um método _subscribe () _.

*3. Testando o RxJava usando um TestSubscriber *

O RxJava vem com uma classe TestSubsriber que nos permite escrever testes que funcionam com um processamento assíncrono de eventos. Este é um observador normal que assina o observável.

Em um teste, podemos examinar o estado de um TestSubscriber e fazer afirmações sobre esse estado:

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

Estamos passando uma instância TestSubscriber para um método _subscribe () _ no observável. Então podemos examinar o estado desse assinante.

*_TestSubscriber_ possui alguns métodos de asserção muito úteis* que usaremos para validar nossas expectativas. O assinante deve receber 5 elementos emitidos por um observador e afirmamos isso chamando o método _assertValueCount () _.

Podemos examinar todos os eventos que um assinante recebeu chamando o método _getOnNextEvents () _.

A chamada do método _assertCompleted () _ verifica se um fluxo no qual o observador está inscrito está concluído. O método _assertNoErrors () _ afirma que não houve erros ao assinar um fluxo.

*4. Testando exceções esperadas *

Às vezes, em nosso processamento, quando um observável está emitindo eventos ou um observador está processando eventos, ocorre um erro. O TestSubscriber possui um método especial para examinar o estado do erro - o método _assertError () _ que usa o tipo de uma exceção como argumento:

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

Estamos criando o observável associado a outro observável usando o método concatWith () _. O segundo observável lança um _RuntimeException ao emitir o próximo evento. Podemos examinar um tipo dessa exceção em um TestSubsciber chamando o método _assertError () _.

O observador que recebe um erro cessa o processamento e termina em um estado não concluído. Esse estado pode ser verificado pelo método _assertNotCompleted () _.

===* 5. Testando Observável com base no tempo *

Digamos que temos um Observable que emite um evento por segundo e queremos testar esse comportamento com um TestSubsciber.

Podemos definir um Observable baseado em tempo usando o método Observable.interval () _ e passar um _TimeUnit como argumento:

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

O observável tick emitirá um novo valor a cada segundo.

No início de um teste, estamos no tempo zero, portanto nosso TestSubscriber não será concluído:

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

Para emular o tempo passando em nosso teste, precisamos usar uma classe http://reactivex.io/RxJava/javadoc/rx/schedulers/TestScheduler.html [TestScheduler] _. Podemos simular essa passagem de um segundo chamando o método _advanceTimeBy () _ em um _TestScheduler:

scheduler.advanceTimeBy(1, TimeUnit.SECONDS);

O método _advanceTimeBy () _ fará com que um observável produza um evento. Podemos afirmar que um evento foi produzido chamando um método _assertValueCount () _:

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

Nossa lista de letters possui 5 elementos; portanto, quando queremos que um observável emita todos os eventos, é necessário passar 6 segundos de processamento. Para emular esses 6 segundos, usamos o método _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"));

Depois de emular o tempo passado, podemos executar asserções em um TestSubscriber. Podemos afirmar que todos os eventos foram produzidos chamando o método _assertValueCount () _.

===* 6. Conclusão*

Neste artigo, examinamos maneiras de testar observadores e observáveis ​​no RxJava. Analisamos uma maneira de testar eventos emitidos, erros e observáveis ​​baseados em tempo.

A implementação de todos esses exemplos e trechos de código pode ser encontrada no GitHub project - este é um projeto do Maven, portanto, deve ser fácil importar e executar como está.