Wie teste ich RxJava?

Wie teste ich RxJava?

1. Überblick

In diesem Artikel werden Möglichkeiten zum Testen von Code untersucht, der mitRxJava geschrieben wurde.

Der typische Fluss, den wir mit RxJava erstellen, besteht aus einemObservable und einemObserver.. Das Observable ist eine Datenquelle, die eine Folge von Elementen ist. Ein oder mehrere Beobachter abonnieren es, um ausgesendete Ereignisse zu erhalten.

In der Regel werden Observer und Observables asynchron in separaten Threads ausgeführt. Dadurch ist der Code auf herkömmliche Weise nur schwer zu testen.

Zum GlückRxJava provides a TestSubscriber class which gives us the ability to test asynchronous, event-driven flow.

2. Testen von RxJava - Der traditionelle Weg

Let’s start with an example - Wir haben eine Folge von Buchstaben, die wir mit einer Folge von ganzen Zahlen von einschließlich 1 komprimieren möchten.

In unserem Test sollte festgestellt werden, dass ein Abonnent, der Ereignisse abhört, die von zipped observable gesendet werden, mit ganzen Zahlen gezippte Buchstaben erhält.

Wenn wir einen solchen Test auf herkömmliche Weise schreiben, müssen wir eine Ergebnisliste führen und diese Liste von einem Beobachter aktualisieren. Das Hinzufügen von Elementen zu einer Liste von Ganzzahlen bedeutet, dass unsere Observable und Observer im selben Thread arbeiten müssen - sie können nicht asynchron arbeiten.

Und so würde uns einer der größten Vorteile von RxJava fehlen - die Verarbeitung von Ereignissen in separaten Threads.

So würde diese eingeschränkte Version des Tests aussehen:

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

Wir aggregieren die Ergebnisse eines Beobachters, indem wir Elemente zu einerresults-Liste hinzufügen. Der Beobachter und das Beobachtbare arbeiten im selben Thread, sodass unsere Behauptung ordnungsgemäß blockiert und darauf wartet, dass die Methodesubscribe()abgeschlossen ist.

3. Testen von RxJava mitTestSubscriber

RxJava wird mit einerTestSubsriber-Klasse geliefert, mit der wir Tests schreiben können, die mit einer asynchronen Verarbeitung von Ereignissen arbeiten. Dies ist ein normaler Beobachter, der das Observable abonniert.

In einem Test können wir den Zustand vonTestSubscriber untersuchen und Aussagen zu diesem Zustand machen:

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

Wir übergeben eineTestSubscriber-Instanz an einesubscribe()-Methode für das Observable. Dann können wir den Zustand dieses Teilnehmers untersuchen.

TestSubscriber has some very useful assertion methods, mit denen wir unsere Erwartungen bestätigen. Der Abonnent sollte 5 emittierte Elemente von einem Beobachter erhalten, und wir behaupten dies, indem wir dieassertValueCount()-Methode aufrufen.

Wir können alle Ereignisse untersuchen, die ein Abonnent durch Aufrufen dergetOnNextEvents()-Methode erhalten hat.

Durch Aufrufen der MethodeassertCompleted()wird geprüft, ob ein Stream, den der Beobachter abonniert hat, abgeschlossen ist. Die MethodeassertNoErrors() bestätigt, dass beim Abonnieren eines Streams keine Fehler aufgetreten sind.

4. Erwartete Ausnahmen testen

Manchmal tritt in unserer Verarbeitung ein Fehler auf, wenn ein Observable Ereignisse ausstrahlt oder ein Observer Ereignisse verarbeitet. TestSubscriber verfügt über eine spezielle Methode zur Prüfung des Fehlerzustands - dieassertError()-Methode, die den Typ einer Ausnahme als Argument verwendet:

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

Wir erstellen das Observable, das mit einem anderen Observable verbunden ist, mithilfe derconcatWith()-Methode. Das zweite beobachtbare Element wirft einRuntimeException, während das nächste Ereignis ausgegeben wird. Wir können einen Typ dieser Ausnahme fürTestSubsciber untersuchen, indem wir die MethodeassertError() aufrufen.

Der Beobachter, der einen Fehler erhält, beendet die Verarbeitung und befindet sich in einem nicht abgeschlossenen Zustand. Dieser Zustand kann mit der MethodeassertNotCompleted()überprüft werden.

5. Testen von zeitbasiertenObservable

Nehmen wir an, wir haben einObservable, das ein Ereignis pro Sekunde ausgibt, und wir möchten dieses Verhalten mit einemTestSubsciber testen.

Wir können ein zeitbasiertesObservable mit der MethodeObservable.interval() definieren und einTimeUnit als Argument übergeben:

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

Das beobachtbaretick gibt jede Sekunde einen neuen Wert aus.

Zu Beginn eines Tests befinden wir uns zum Zeitpunkt Null, sodass unsereTestSubscriber nicht abgeschlossen sind:

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

Um die in unserem Test verstrichene Zeit zu emulieren, müssen wir eineTestScheduler-Klasse verwenden. Wir können diesen Durchlauf von einer Sekunde simulieren, indem wir die MethodeadvanceTimeBy() fürTestScheduler aufrufen:

scheduler.advanceTimeBy(1, TimeUnit.SECONDS);

Mit der MethodeadvanceTimeBy()wird ein beobachtbares Ereignis erzeugt. Wir können behaupten, dass ein Ereignis durch Aufrufen einerassertValueCount()-Methode erzeugt wurde:

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

Unsere Liste vonletters enthält 5 Elemente. Wenn also ein Observable alle Ereignisse ausgeben soll, müssen 6 Sekunden der Verarbeitung vergehen. Um diese 6 Sekunden zu emulieren, verwenden wir dieadvanceTimeTo()-Methode:

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

Nach dem Emulieren der verstrichenen Zeit können wir Assertions fürTestSubscriber ausführen. Wir können behaupten, dass alle Ereignisse durch Aufrufen derassertValueCount()-Methode erzeugt wurden.

6. Fazit

In diesem Artikel haben wir Möglichkeiten zum Testen von Beobachtern und Observablen in RxJava untersucht. Wir untersuchten eine Methode zum Testen von emittierten Ereignissen, Fehlern und zeitbasierten Observablen.

Die Implementierung all dieser Beispiele und Codefragmente finden Sie inGitHub project - dies ist ein Maven-Projekt, daher sollte es einfach zu importieren und auszuführen sein, wie es ist.