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á.