Testing av reaktive strømmer ved hjelp av StepVerifier og TestPublisher

1. Oversikt

I denne opplæringen vil vi se nærmere på å teste reaktive strømmer med StepVerifier og TestPublisher.

Vi vil basere etterforskningen vår på en Vårreaktor applikasjon som inneholder en kjede av reaktoroperasjoner.

2. Maven-avhengigheter

Spring Reactor kommer med flere klasser for testing av reaktive strømmer.

Vi kan få disse ved å legge til reaktortest avhengighet:

 io.projektreaktortest-testtest 3.2.3.FRIGIVELSE 

3. StepVerifier

Generelt, reaktortest har to hovedbruksområder:

  • lage en trinnvis test med StepVerifier
  • produsere forhåndsdefinerte data med TestPublisher for å teste nedstrøms operatører

Det vanligste tilfellet ved testing av reaktive strømmer er når vi har et forlag (a Flux eller Mono) definert i koden vår. Vi vil vite hvordan det oppfører seg når noen abonnerer.

Med StepVerifier API, kan vi definere våre forventninger til publiserte elementer når det gjelder hvilke elementer vi forventer og hva som skjer når strømmen vår fullføres.

Først av alt, la oss opprette en forlegger med noen operatører.

Vi bruker en Flux.just (T-elementer). Denne metoden vil skape en Flux som avgir gitte elementer og deretter fullfører.

Siden avanserte operatører er utenfor omfanget av denne artikkelen, oppretter vi bare en enkel utgiver som bare skriver ut fire bokstaver som er tilordnet store bokstaver:

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

3.1. Steg-for-trinn-scenario

La oss nå teste vår kilde med StepVerifier for å teste hva som vil skje når noen abonnerer:

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

Først lager vi en StepVerifier byggmester med skape metode.

Deretter pakker vi inn Flux kilde, som er under test. Det første signalet bekreftes med expectNext (T-element), men seriøst, vi kan overføre et hvilket som helst antall elementer til forvent Neste.

Vi kan også bruke expectNextMatches og gi en Predikere for en mer tilpasset kamp.

For vår siste forventning forventer vi at strømmen vår blir fullført.

Og endelig, vi bruker bekrefte() for å utløse testen vår.

3.2. Unntak i StepVerifier

La oss nå sammenkoble vår Flux forlegger med Mono.

Vi får dette Mono avslutte umiddelbart med en feil når du abonnerer på:

Fluxfeil = source.concatWith (Mono.error (ny IllegalArgumentException ("Vår melding")));

Nå, etter fire alle elementene, vi forventer at strømmen vår avsluttes med unntak:

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

Vi kan bare bruke en metode for å verifisere unntak. De OnError signalet varsler abonnenten om det utgiveren er lukket med en feiltilstand. Derfor kan vi ikke legge til flere forventninger etterpå.

Hvis det ikke er nødvendig å sjekke typen og meldingen til unntaket på en gang, kan vi bruke en av de dedikerte metodene:

  • expectError () - forvent noen form for feil
  • expectError (klasse clazz) – forvent en feil av en bestemt type
  • expectErrorMessage (strengfeilmelding) - forventer en feil med en spesifikk melding
  • expectErrorMatches (Predicate predicate) - forvent en feil som samsvarer med et gitt predikat
  • expectErrorSatisfies (Consumer assertionConsumer) - konsumere en Kastbar for å gjøre en tilpasset påstand

3.3. Testing av tidsbaserte forlag

Noen ganger er forleggerne våre tidsbaserte.

Anta for eksempel at i vår virkelige applikasjon, vi har en dags forsinkelse mellom hendelsene. Nå vil vi åpenbart ikke at testene våre skal kjøre en hel dag for å verifisere forventet oppførsel med en slik forsinkelse.

StepVerifier.withVirtualTime Builder er designet for å unngå langvarige tester.

Vi lager en byggmester ved å ringe medVirtualTime.Merk at denne metoden ikke tar Fluxsom innspill. I stedet tar det en Leverandør, som lat skaper en forekomst av de testede Flux etter at planleggeren er satt opp.

For å demonstrere hvordan vi kan teste for en forventet forsinkelse mellom hendelser, la oss lage en Flux med et intervall på ett sekund som går i to sekunder. Hvis timeren går riktig, bør vi bare 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 ();

Merk at vi bør unngå å sette i gang Flux tidligere i koden og deretter ha Leverandør returnerer denne variabelen. I stedet, vi bør alltid instantiere Flux inne i lambda.

Det er to store forventningsmetoder som tar for seg tid:

  • thenAwait (Varighet varighet) - stopper evalueringen av trinnene midlertidig; nye hendelser kan forekomme i løpet av denne tiden
  • expectNoEvent (Varighet varighet) - mislykkes når noen hendelser vises under varighet; sekvensen vil passere med et gitt varighet

Vær oppmerksom på at det første signalet er abonnementshendelsen hver expectNoEvent (Varighet varighet) skal gå foran expectSubscription ().

3.4. Påstander etter utførelse med StepVerifier

Så som vi har sett, er det greit å beskrive forventningene våre trinn for trinn.

Derimot, noen ganger må vi bekrefte ytterligere tilstand etter at hele scenariet har blitt spilt vellykket.

La oss lage et tilpasset utgiver. Det vil sende ut noen få elementer, deretter fullføre, pause og sende ut ett element til, som vi vil slippe:

Flukskilde = 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 -> nummer% 2 == 0);

Vi forventer at den vil avgi en 2, men slippe en 4, siden vi ringte emitter.complete først.

Så la oss bekrefte denne oppførselen ved å bruke verifiser deretterAssertThat. Denne metoden returnerer StepVerifier.Assertions som vi kan legge til påstandene våre:

@Test offentlig ugyldig droppetElementer () {StepVerifier.create (kilde) .expectNext (2) .expectComplete () .verifyThenAssertThat () .hasDropped (4) .tookLessThan (Duration.ofMillis (1050)); }

4. Å produsere data med TestPublisher

Noen ganger kan det hende vi trenger noen spesielle data for å utløse de valgte signalene.

For eksempel kan vi ha en veldig spesiell situasjon som vi ønsker å teste.

Alternativt kan vi velge å implementere vår egen operatør og vil teste hvordan den oppfører seg.

For begge tilfeller kan vi bruke TestPublisher, hvilken lar oss programmatisk utløse diverse signaler:

  • neste (T-verdi) eller neste (T-verdi, T-hvile) - sende ett eller flere signaler til abonnenter
  • emit (T-verdi) - samme som neste t) men påberoper seg fullstendig() etterpå
  • fullstendig() - avslutter en kilde med fullstendig signal
  • feil (kastbar tr) - avslutter en kilde med en feil
  • flux () - praktisk metode for å pakke inn en TestPublisher inn i Flux
  • mono () - samme oss flux () men brytes til en Mono

4.1. Opprette en TestPublisher

La oss lage en enkel TestPublisher som sender ut noen få signaler og deretter avsluttes med unntak:

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

4.2. TestPublisher i aksjon

Som vi nevnte tidligere, kan det være lurt å utløse et fint valgt signal som samsvarer nøye med en bestemt situasjon.

Nå er det spesielt viktig i dette tilfellet at vi har full beherskelse over datakilden. For å oppnå dette kan vi igjen stole på TestPublisher.

La oss først lage en klasse som bruker Flux som konstruktorparameter for å utføre operasjonen getUpperCase ():

klasse UppercaseConverter {privat slutt Flux kilde; UppercaseConverter (Flux source) {this.source = source; } Flux getUpperCase () {retur kilde .map (streng :: toUpperCase); }}

Anta at Store bokstaver er vår klasse med kompleks logikk og operatører, og vi må levere veldig spesifikke data fra kilde forlegger.

Vi kan enkelt oppnå dette med Testutgiver:

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

I dette eksemplet lager vi en test Flux utgiver i Store bokstaver konstruktørparameter. Så, vår TestPublisher avgir tre elementer og fullfører.

4.3. Feil oppførsel TestPublisher

På den andre siden, vi kan skape en dårlig oppførsel TestPublisher med createNonCompliant fabrikkmetode. Vi må gi konstruktøren en enumverdi fra TestPublisher. Overtredelse. Disse verdiene spesifiserer hvilke deler av spesifikasjonene utgiveren vår kan overse.

La oss ta en titt på a TestPublisher som ikke vil kaste en NullPointerException for null element:

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

I tillegg til ALLOW_NULL, vi kan også bruke TestPublisher.Overtredelse til:

  • REQUEST_OVERFLOW - tillater å ringe neste () uten å kaste en IllegalStateException når det er utilstrekkelig antall forespørsler
  • CLEANUP_ON_TERMINATE - tillater sending av et avslutningssignal flere ganger på rad
  • DEFER_CANCELLATION - lar oss ignorere kanselleringssignaler og fortsette med emitterende elementer

5. Konklusjon

I denne artikkelen, vi diskuterte forskjellige måter å teste reaktive strømmer fra Vårreaktor prosjekt.

Først så vi hvordan vi skulle bruke StepVerifier å teste utgivere. Så så vi hvordan vi skulle bruke TestPublisher. På samme måte så vi hvordan vi skulle operere med en dårlig oppførsel TestPublisher.

Som vanlig kan implementeringen av alle eksemplene våre finnes i Github-prosjektet.


$config[zx-auto] not found$config[zx-overlay] not found