Test af reaktive streams ved hjælp af StepVerifier og TestPublisher

1. Oversigt

I denne vejledning ser vi nærmere på test af reaktive streams med StepVerifier og Testudgiver.

Vi baserer vores undersøgelse på en Spring Reactor applikation indeholdende en kæde af reaktoroperationer.

2. Maven-afhængigheder

Spring Reactor leveres med flere klasser til test af reaktive strømme.

Vi kan få disse ved at tilføje reaktortest afhængighed:

 io.projektreaktor reaktortest 3.2.3.FRIGIVELSE 

3. StepVerifier

Generelt, reaktortest har to hovedanvendelser:

  • oprette en trin-for-trin test med StepVerifier
  • producere foruddefinerede data med Testudgiver at teste downstream-operatører

Det mest almindelige tilfælde ved test af reaktive streams er, når vi har en udgiver (a Strøm eller Mono) defineret i vores kode. Vi vil vide, hvordan det opfører sig, når nogen abonnerer.

Med StepVerifier API, kan vi definere vores forventninger til offentliggjorte elementer med hensyn til hvilke elementer vi forventer, og hvad der sker, når vores stream er færdig.

Lad os først og fremmest oprette en udgiver med nogle operatører.

Vi bruger en Flux.just (T-elementer). Denne metode vil skabe en Strøm der udsender givne elementer og derefter fuldfører.

Da avancerede operatører er uden for denne artikels anvendelsesområde, opretter vi bare en simpel udgiver, der kun udfører navne på fire bogstaver, der er kortlagt med store bogstaver:

Flux kilde = Flux.just ("John", "Monica", "Mark", "Cloe", "Frank", "Casper", "Olivia", "Emily", "Cate"). Filter (navn -> navn .length () == 4) .map (String :: toUpperCase);

3.1. Trin-for-trin-scenarie

Lad os nu teste vores kilde med StepVerifier for at teste, hvad der vil ske, når nogen abonnerer:

StepVerifier .create (kilde) .expectNext ("JOHN") .expectNextMatches (navn -> name.startsWith ("MA")) .expectNext ("CLOE", "CATE") .expectComplete () .verify ();

Først opretter vi en StepVerifier bygherre med skab metode.

Derefter pakker vi vores Strøm kilde, som er under test. Det første signal bekræftes med expectNext (T-element), men virkelig, vi kan overføre et vilkårligt antal elementer til forventNæste.

Vi kan også bruge expectNextMatches og give en Prædikat for et mere tilpasset match.

For vores sidste forventning forventer vi, at vores stream er færdig.

Og endelig, vi bruger verificere() for at udløse vores test.

3.2. Undtagelser i StepVerifier

Lad os nu sammenkæde vores Strøm udgiver med Mono.

Vi får dette Mono afslut med det samme med en fejl, når du abonnerer på:

Fluxfejl = source.concatWith (Mono.error (ny IllegalArgumentException ("Vores meddelelse")));

Nu, efter fire alle elementer, vi forventer, at vores strøm ophører med en undtagelse:

StepVerifier .create (error) .expectNextCount (4) .expectErrorMatches (throwable -> throwable instanceof IllegalArgumentException && throwable.getMessage (). Equals ("Our message")) .verify ();

Vi kan kun bruge en metode til at verificere undtagelser. Det OnError signal underretter abonnenten om det udgiveren lukkes med en fejltilstand. Derfor kan vi ikke tilføje flere forventninger bagefter.

Hvis det ikke er nødvendigt at kontrollere undtagelsestypen og meddelelsen på en gang, kan vi bruge en af ​​de dedikerede metoder:

  • expectError () - forvent enhver form for fejl
  • expectError (klasse clazz) – forvent en fejl af en bestemt type
  • expectErrorMessage (String errorMessage) - forventer en fejl med en bestemt meddelelse
  • expectErrorMatches (Predicate predicate) - forvent en fejl, der matcher et givet prædikat
  • expectErrorSatisfies (Consumer assertionConsumer) - forbruge en Kan kastes for at gøre en brugerdefineret påstand

3.3. Test af tidsbaserede udgivere

Nogle gange er vores udgivere tidsbaserede.

Antag for eksempel, at i vores virkelige applikation, vi har en dags forsinkelse mellem begivenhederne. Nu, selvfølgelig, ønsker vi ikke, at vores tests kører en hel dag for at verificere forventet adfærd med en sådan forsinkelse.

StepVerifier.withVirtualTime builder er designet til at undgå langvarige tests.

Vi opretter en bygherre ved at ringe medVirtualTime.Bemærk, at denne metode ikke tager Strømsom input. I stedet tager det en Leverandør, som dovent opretter en forekomst af den testede Strøm efter at planlæggeren er konfigureret.

Lad os oprette en for at demonstrere, hvordan vi kan teste for en forventet forsinkelse mellem begivenheder Strøm med et interval på et sekund, der løber i to sekunder. Hvis timeren kører korrekt, skal vi kun få to elementer:

StepVerifier .withVirtualTime (() -> Flux.interval (Duration.ofSeconds (1)). Take (2)) .expectSubscription () .expectNoEvent (Duration.ofSeconds (1)) .expectNext (0L) .thenAwait (Duration.ofSeconds (1)) .expectNext (1L) .verifyComplete ();

Bemærk, at vi bør undgå at instantiere Strøm tidligere i koden og derefter have Leverandør returnerer denne variabel. I stedet, vi skal altid instantiere Strøm inde i lambda.

Der er to store forventningsmetoder, der beskæftiger sig med tiden:

  • thenAwait (Varighed varighed) - sætter evalueringen af ​​trinnene på pause nye begivenheder kan forekomme i løbet af denne periode
  • expectNoEvent (Varighed varighed) - mislykkes, når en begivenhed vises under varighed; sekvensen vil passere med en given varighed

Bemærk, at det første signal er abonnementsbegivenheden, så hver expectNoEvent (Varighed varighed) skal forud for expectSubscription ().

3.4. Påstande efter udførelse med StepVerifier

Så som vi har set, er det ligetil at beskrive vores forventninger trin for trin.

Imidlertid, nogle gange er vi nødt til at verificere yderligere tilstand, efter at hele vores scenarie er spillet.

Lad os oprette en brugerdefineret udgiver. Det udsender et par elementer, derefter fuldfører, sætter pause og udsender endnu et element, som vi vil slippe:

Fluxkilde = Flux.create (emitter -> {emitter.next (1); emitter.next (2); emitter.next (3); emitter.complete (); prøv {Thread.sleep (1000);} fangst ( InterruptedException e) {e.printStackTrace ();} emitter.next (4);}). Filter (nummer -> antal% 2 == 0);

Vi forventer, at det udsender en 2, men dropper en 4, da vi ringede emitter.complete først.

Så lad os kontrollere denne adfærd ved hjælp af verificereDaAssertTat. Denne metode vender tilbage StepVerifier.Assertions som vi kan tilføje vores påstande om:

@Test offentligt ugyldigt droppetElements () {StepVerifier.create (kilde) .expectNext (2) .expectComplete () .verifyThenAssertThat () .hasDropped (4) .tookLessThan (Duration.ofMillis (1050)); }

4. Producerer data med Testudgiver

Nogle gange har vi muligvis brug for nogle specielle data for at udløse de valgte signaler.

For eksempel kan vi have en meget særlig situation, som vi vil teste.

Alternativt kan vi vælge at implementere vores egen operatør og vil teste, hvordan den opfører sig.

I begge tilfælde kan vi bruge Testudgiver, hvilken giver os mulighed for programmatisk at udløse diverse signaler:

  • næste (T-værdi) eller næste (T-værdi, T-hvile) - sende et eller flere signaler til abonnenter
  • udsender (T-værdi) - samme som næste (T) men påberåber sig komplet() bagefter
  • komplet() - afslutter en kilde med komplet signal
  • fejl (kastbar tr) - afslutter en kilde med en fejl
  • strøm() - praktisk metode til at pakke en Testudgiver ind i Strøm
  • mono () - samme os strøm() men indpakker til en Mono

4.1. Oprettelse af en Testudgiver

Lad os oprette en simpel Testudgiver der udsender et par signaler og derefter afsluttes med en undtagelse:

TestPublisher .create () .next ("First", "Second", "Third") .error (new RuntimeException ("Message"));

4.2. Testudgiver i aktion

Som vi nævnte tidligere, vil vi måske nogle gange udløse et fint valgt signal, der passer nøje til en bestemt situation.

Nu er det især vigtigt i dette tilfælde, at vi har fuldstændig beherskelse af datakilden. For at opnå dette kan vi igen stole på Testudgiver.

Lad os først oprette en klasse, der bruger Strøm som konstruktorparameter til at udføre handlingen getUpperCase ():

klasse OpercaseConverter {privat endelig Flux kilde; UppercaseConverter (Flux-kilde) {this.source = kilde; } Flux getUpperCase () {return source .map (String :: toUpperCase); }}

Antag at Store bogstaver er vores klasse med kompleks logik og operatører, og vi skal levere meget specifikke data fra kilde forlægger.

Vi kan let opnå dette med Testudgiver:

endelig TestPublisher testPublisher = TestPublisher.create (); UppercaseConverter uppercaseConverter = ny UppercaseConverter (testPublisher.flux ()); StepVerifier.create (uppercaseConverter.getUpperCase ()). Derefter (() -> testPublisher.emit ("aA", "bb", "ccc")) .expectNext ("AA", "BB", "CCC"). verificereKomplet ();

I dette eksempel opretter vi en test Strøm udgiver i Store bogstaver konstruktørparameter. Så vores Testudgiver udsender tre elementer og udfylder.

4.3. Fejlagtigt opførsel Testudgiver

På den anden side, vi kan skabe en dårlig opførsel Testudgiver med createNonCompliant fabriksmetode. Vi er nødt til at give konstruktøren en enumværdi fra TestPublisher. Overtrædelse. Disse værdier angiver, hvilke dele af specifikationer vores udgiver kan overse.

Lad os se på en Testudgiver der kaster ikke en NullPointerException til nul element:

TestPublisher .createNoncompliant (TestPublisher.Violation.ALLOW_NULL) .emit ("1", "2", null, "3"); 

I tillæg til ALLOW_NULL, vi kan også bruge TestPublisher. Overtrædelse til:

  • REQUEST_OVERFLOW - tillader opkald Næste() uden at smide en IllegalStateException når der er et utilstrækkeligt antal anmodninger
  • CLEANUP_ON_TERMINATE - tillader afsendelse af ethvert afslutningssignal flere gange i træk
  • DEFER_CANCELLATION - giver os mulighed for at ignorere annulleringssignaler og fortsætte med emitterende elementer

5. Konklusion

I denne artikel vi diskuterede forskellige måder at teste reaktive strømme fra Spring Reactor projekt.

Først så vi, hvordan man bruger StepVerifier at teste udgivere. Derefter så vi, hvordan man bruger Testudgiver. På samme måde så vi, hvordan man opererer med en dårlig opførsel Testudgiver.

Som sædvanligt kan implementeringen af ​​alle vores eksempler findes i Github-projektet.