Come testare RxJava?

1. Panoramica

In questo articolo, esamineremo i modi per testare il codice scritto utilizzando RxJava.

Il flusso tipico che stiamo creando con RxJava è costituito da un Observable e un Observer. L'osservabile è una fonte di dati che è una sequenza di elementi. Uno o più osservatori si iscrivono per ricevere gli eventi emessi.

In genere, l'osservatore e gli osservabili vengono eseguiti in thread separati in modo asincrono, il che rende il codice difficile da testare in modo tradizionale.

Fortunatamente, RxJava fornisce una classe TestSubscriber che ci dà la possibilità di testare un flusso asincrono guidato dagli eventi.

2. Test di RxJava - il modo tradizionale

Cominciamo con un esempio : abbiamo una sequenza di lettere che vogliamo comprimere con una sequenza di numeri interi da 1 incluso.

Il nostro test dovrebbe affermare che un sottoscrittore che ascolta gli eventi emessi da osservabile zippato riceve lettere zippate con numeri interi.

Scrivere un test di questo tipo in modo tradizionale significa che dobbiamo conservare un elenco di risultati e aggiornare tale elenco da un osservatore. Aggiungere elementi a un elenco di numeri interi significa che il nostro osservabile e gli osservatori devono lavorare nello stesso thread: non possono funzionare in modo asincrono.

E quindi ci mancherebbe uno dei maggiori vantaggi di RxJava: l'elaborazione di eventi in thread separati.

Ecco come sarebbe la versione limitata del test:

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

Stiamo aggregando i risultati di un osservatore aggiungendo elementi a un elenco di risultati . L'osservatore e l'osservabile lavorano nello stesso thread, quindi la nostra asserzione si blocca correttamente e attende il completamento di un metodo subscribe () .

3. Test di RxJava utilizzando un TestSubscriber

RxJava viene fornito con una classe TestSubsriber che ci consente di scrivere test che funzionano con un'elaborazione asincrona degli eventi. Questo è un normale osservatore che sottoscrive l'osservabile.

In un test, possiamo esaminare lo stato di un TestSubscriber e fare asserzioni su quello stato:

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

Stiamo passando un'istanza TestSubscriber a un metodo subscribe () sull'osservabile . Quindi possiamo esaminare lo stato di questo abbonato.

TestSubscriber ha alcuni metodi di asserzione molto utili che useremo per convalidare le nostre aspettative. L'abbonato dovrebbe ricevere 5 elementi emessi da un osservatore e noi lo affermiamo chiamando ilmetodo assertValueCount () .

Possiamo esaminare tutti gli eventi ricevuti da un abbonato chiamando il metodo getOnNextEvents () .

La chiamata al metodo assertCompleted () controlla se un flusso a cui l'osservatore è iscritto è stato completato. Il metodo assertNoErrors () afferma che non ci sono stati errori durante la sottoscrizione a un flusso.

4. Verifica delle eccezioni previste

A volte nella nostra elaborazione, quando un osservabile emette eventi o un osservatore elabora eventi, si verifica un errore. Il TestSubscriber ha un metodo speciale per esaminare lo stato di errore: il metodo assertError () che accetta il tipo di eccezione come argomento:

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

Stiamo creando l'osservabile che viene unito a un altro osservabile utilizzando il metodo concatWith () . Il secondo osservabile genera una RuntimeException durante l'emissione dell'evento successivo. Possiamo esaminare un tipo di quell'eccezione su TestSubsciber chiamando il metodo assertError () .

L'osservatore che riceve un errore interrompe l'elaborazione e si ritrova in uno stato non completato. Questo stato può essere verificato con il metodo assertNotCompleted () .

5. Test dell'osservabile basato sul tempo

Diciamo che abbiamo un Observable che emette un evento al secondo e vogliamo testare quel comportamento con un TestSubsciber .

Possiamo definire un Observable basato sul tempo utilizzando il metodo Observable.interval () e passare un TimeUnit come argomento:

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

Il segno di spunta osservabile emetterà un nuovo valore ogni secondo.

All'inizio di un test siamo al tempo zero, quindi il nostro TestSubscriber non sarà completato:

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

Per emulare il tempo che passa nel nostro test dobbiamo usare una classe TestScheduler . Possiamo simulare quel passaggio di un secondo chiamando il metodo advanceTimeBy () su un TestScheduler :

scheduler.advanceTimeBy(1, TimeUnit.SECONDS);

Il metodo advanceTimeBy () farà in modo che un osservabile produca un evento. Possiamo affermare che un evento è stato prodotto chiamando un metodo assertValueCount () :

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

Il nostro elenco di lettere contiene 5 elementi, quindi quando vogliamo che un osservabile emetta tutti gli eventi, devono passare 6 secondi di elaborazione. Per emulare quei 6 secondi, usiamo il metodo 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"));

Dopo aver emulato il tempo trascorso, possiamo eseguire asserzioni su un TestSubscriber . Possiamo affermare che tutti gli eventi sono stati prodotti chiamando il metodo assertValueCount () .

6. Conclusione

In questo articolo, abbiamo esaminato i modi per testare osservatori e osservabili in RxJava. Abbiamo esaminato un modo per testare eventi emessi, errori e osservabili basati sul tempo.

L'implementazione di tutti questi esempi e frammenti di codice può essere trovata nel progetto GitHub: questo è un progetto Maven, quindi dovrebbe essere facile da importare ed eseguire così com'è.