Comment tester RxJava?

Comment tester RxJava?

1. Vue d'ensemble

Dans cet article, nous examinerons les moyens de tester le code écrit à l'aide deRxJava.

Le flux typique que nous créons avec RxJava se compose d'unObservable et d'unObserver. L'observable est une source de données qui est une séquence d'éléments. Un ou plusieurs observateurs s'y abonnent pour recevoir les événements émis.

En règle générale, l'observateur et les observables sont exécutés dans des threads séparés de manière asynchrone, ce qui rend le code difficile à tester de manière traditionnelle.

Heureusement,RxJava provides a TestSubscriber class which gives us the ability to test asynchronous, event-driven flow.

2. Tester RxJava - La méthode traditionnelle

Let’s start with an example - nous avons une séquence de lettres que nous voulons compresser avec une séquence d'entiers à partir de 1 inclus.

Notre test doit confirmer qu'un abonné qui écoute les événements émis par un observable compressé reçoit des lettres compressées avec des entiers.

Ecrire un tel test de manière traditionnelle signifie que nous devons conserver une liste de résultats et la mettre à jour par un observateur. L'ajout d'éléments à une liste d'entiers signifie que notre observable et les observateurs doivent travailler dans le même thread - ils ne peuvent pas fonctionner de manière asynchrone.

Et ainsi, nous manquerions l'un des plus grands avantages de RxJava - le traitement des événements dans des threads séparés.

Voici à quoi ressemblerait cette version limitée du 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"));

Nous agrégons les résultats d'un observateur en ajoutant des éléments à une listeresults. L'observateur et l'observable travaillent dans le même thread, donc notre assertion se bloque correctement et attend la fin d'une méthodesubscribe().

3. Test de RxJava avec unTestSubscriber

RxJava est livré avec une classeTestSubsriber qui nous permet d'écrire des tests qui fonctionnent avec un traitement asynchrone des événements. C'est un observateur normal qui s'abonne à l'observable.

Dans un test, nous pouvons examiner l'état d'unTestSubscriber et faire des affirmations sur cet état:

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

Nous passons une instanceTestSubscriber à une méthodesubscribe() sur l'observable. Ensuite, nous pouvons examiner l'état de cet abonné.

TestSubscriber has some very useful assertion methods que nous utiliserons pour valider nos attentes. L'abonné doit recevoir 5 éléments émis par un observateur et nous l'affirmons en appelant la méthodeassertValueCount().

Nous pouvons examiner tous les événements qu'un abonné a reçus en appelant la méthodegetOnNextEvents().

L'appel de la méthodeassertCompleted() vérifie si un flux auquel l'observateur est abonné est terminé. La méthodeassertNoErrors() affirme qu'il n'y a eu aucune erreur lors de l'abonnement à un flux.

4. Test des exceptions attendues

Parfois, dans notre traitement, lorsqu'un observable émet des événements ou qu'un observateur traite des événements, une erreur se produit. LeTestSubscriber a une méthode spéciale pour examiner l'état d'erreur - la méthodeassertError() qui prend le type d'une exception comme argument:

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

Nous créons l'observable qui est jointe à une autre observable en utilisant la méthodeconcatWith(). La deuxième observable lance unRuntimeException lors de l'émission de l'événement suivant. Nous pouvons examiner un type de cette exception sur unTestSubsciber en appelant la méthodeassertError().

L'observateur qui reçoit une erreur cesse le traitement et finit dans un état non terminé. Cet état peut être vérifié par la méthodeassertNotCompleted().

5. Test desObservable en fonction du temps

Disons que nous avons unObservable qui émet un événement par seconde et que nous voulons tester ce comportement avec unTestSubsciber.

Nous pouvons définir unObservable basé sur le temps en utilisant la méthodeObservable.interval() et passer unTimeUnit comme argument:

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

L'observabletick émettra une nouvelle valeur toutes les secondes.

Au début d'un test, nous sommes au temps zéro, donc nosTestSubscriber ne seront pas terminés:

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

Pour émuler le temps qui passe dans notre test, nous devons utiliser une classeTestScheduler. Nous pouvons simuler cette passe d'une seconde en appelant la méthodeadvanceTimeBy() sur unTestScheduler:

scheduler.advanceTimeBy(1, TimeUnit.SECONDS);

La méthodeadvanceTimeBy() fera en sorte qu'un observable produise un événement. Nous pouvons affirmer qu'un événement a été produit en appelant une méthodeassertValueCount():

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

Notre liste deletters contient 5 éléments, donc lorsque nous voulons que l'observable émette tous les événements, 6 secondes de traitement doivent s'écouler. Pour émuler ces 6 secondes, nous utilisons la méthodeadvanceTimeTo():

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

Après avoir émulé le temps passé, nous pouvons exécuter des assertions sur unTestSubscriber. Nous pouvons affirmer que tous les événements ont été produits en appelant la méthodeassertValueCount().

6. Conclusion

Dans cet article, nous avons examiné les moyens de tester les observateurs et les observables dans RxJava. Nous avons étudié un moyen de tester les événements émis, les erreurs et les observables temporels.

L'implémentation de tous ces exemples et extraits de code peut être trouvée dans leGitHub project - il s'agit d'un projet Maven, il devrait donc être facile à importer et à exécuter tel quel.