Reactieve streams testen met StepVerifier en TestPublisher

1. Overzicht

In deze tutorial zullen we het testen van reactieve streams met StepVerifier en TestPublisher.

We zullen ons onderzoek baseren op een Lente Reactor applicatie die een keten van reactoroperaties bevat.

2. Maven afhankelijkheden

Spring Reactor wordt geleverd met verschillende klassen voor het testen van reactieve stromen.

We kunnen deze krijgen door het reactortest afhankelijkheid:

 io.projectreactor reactor-test test 3.2.3.RELEASE 

3. StepVerifier

Over het algemeen, reactortest heeft twee hoofdtoepassingen:

  • het maken van een stapsgewijze test met StepVerifier
  • het produceren van voorgedefinieerde gegevens met TestPublisher om stroomafwaartse operators te testen

Het meest voorkomende geval bij het testen van reactieve streams is wanneer we een uitgever hebben (een Flux of Mono) gedefinieerd in onze code. We willen weten hoe het zich gedraagt ​​als iemand zich abonneert.

Met de StepVerifier API kunnen we onze verwachtingen van gepubliceerde elementen definiëren in termen van welke elementen we verwachten en wat er gebeurt als onze stream is voltooid.

Laten we eerst een publisher maken met een aantal operators.

We gebruiken een Flux.just (T-elementen). Met deze methode wordt een Flux die bepaalde elementen uitzendt en vervolgens voltooit.

Omdat geavanceerde operators buiten het bestek van dit artikel vallen, maken we gewoon een eenvoudige uitgever die alleen vierletterige namen weergeeft die zijn toegewezen aan hoofdletters:

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

3.1. Stapsgewijs scenario

Laten we nu onze testen bron met StepVerifier om te testen wat er gebeurt als iemand zich abonneert:

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

Eerst maken we een StepVerifier bouwer met de creëren methode.

Vervolgens wikkelen we onze Flux bron, die wordt getest. Het eerste signaal wordt geverifieerd verwachtNext (T-element), maar echt, we kunnen een willekeurig aantal elementen doorgeven aan verwachtenVolgende.

We kunnen ook gebruik maken van verwachtenNextMatches en geef een Predikaat voor een meer aangepaste match.

Voor onze laatste verwachting verwachten we dat onze stream is voltooid.

En tenslotte, we gebruiken verifiëren() om onze test te activeren.

3.2. Uitzonderingen in StepVerifier

Laten we nu onze Flux uitgever met Mono.

We zullen dit hebben Mono onmiddellijk beëindigen met een fout wanneer u zich abonneert op:

Flux-fout = source.concatWith (Mono.error (nieuwe IllegalArgumentException ("Ons bericht")));

Nu, na vier alle elementen, we verwachten dat onze stream met een uitzondering wordt beëindigd:

StepVerifier .create (fout) .expectNextCount (4) .expectErrorMatches (throwable -> throwable instantie van IllegalArgumentException && throwable.getMessage (). Equals ("Ons bericht")) .verify ();

We kunnen slechts één methode gebruiken om uitzonderingen te verifiëren. De OnError signaal meldt de abonnee dat de uitgever is gesloten met een foutstatus. Daarom kunnen we achteraf niet meer verwachtingen toevoegen.

Als het niet nodig is om het type en het bericht van de uitzondering in één keer te controleren, kunnen we een van de speciale methoden gebruiken:

  • verwachtenError () - verwacht elke vorm van fout
  • verwachtError (Class clazz) – verwacht een fout van een bepaald type
  • verwachtenErrorMessage (String errorMessage) - verwacht een fout met een specifiek bericht
  • verwachtenErrorMatches (predikaat predikaat) - verwacht een fout die overeenkomt met een bepaald predikaat
  • verwachtenErrorSatisfies (Consumer assertionConsumer) - consumeer een Gooibaar om een ​​aangepaste bewering te doen

3.3. Tijdgebaseerde uitgevers testen

Soms zijn onze uitgevers gebaseerd op tijd.

Stel bijvoorbeeld dat in onze real-life applicatie, we hebben een vertraging van één dag tussen evenementen. Nu willen we natuurlijk niet dat onze tests een hele dag duren om verwacht gedrag met zo'n vertraging te verifiëren.

StepVerifier.withVirtualTime builder is ontworpen om langdurige tests te vermijden.

We creëren een bouwer door te bellen met VirtualTime.Merk op dat deze methode niet werkt Fluxals input. In plaats daarvan is een Leverancier, die lui een exemplaar van het geteste Flux nadat de planner is ingesteld.

Om te laten zien hoe we kunnen testen op een verwachte vertraging tussen gebeurtenissen, maken we een Flux met een interval van één seconde dat twee seconden duurt. Als de timer correct werkt, zouden we slechts twee elementen moeten krijgen:

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

Merk op dat we het instantiëren van de Flux eerder in de code en dan met de Leverancier deze variabele retourneren. In plaats daarvan, we moeten altijd instantiëren Flux binnen de lambda.

Er zijn twee belangrijke verwachtingsmethoden die met tijd omgaan:

  • thenAwait (Duur duur) - pauzeert de evaluatie van de stappen; er kunnen zich gedurende deze tijd nieuwe gebeurtenissen voordoen
  • verwachtenNoEvent (Duur duur) - mislukt wanneer een gebeurtenis optreedt tijdens de looptijd; de reeks zal voorbijgaan met een gegeven looptijd

Merk op dat het eerste signaal de abonnementsgebeurtenis is, dus elke verwachtenNoEvent (Duur duur) moet worden voorafgegaan met verwachtenSabonnement ().

3.4. Beweringen na uitvoering met StepVerifier

Zoals we hebben gezien, is het dus eenvoudig om onze verwachtingen stap voor stap te beschrijven.

Echter, soms moeten we de aanvullende status verifiëren nadat ons hele scenario met succes is afgespeeld.

Laten we een aangepaste uitgever maken. Het zal een paar elementen uitzenden, dan voltooien, pauzeren en nog een element uitzenden, dat we zullen laten vallen:

Flux source = Flux.create (emitter -> {emitter.next (1); emitter.next (2); emitter.next (3); emitter.complete (); probeer {Thread.sleep (1000);} catch ( InterruptedException e) {e.printStackTrace ();} emitter.next (4);}). Filter (nummer -> nummer% 2 == 0);

We verwachten dat het een 2 zal uitzenden, maar laat een 4 vallen, aangezien we hebben gecalld emitter.com compleet eerste.

Laten we dit gedrag dus verifiëren met verifieerThenAssertThat. Deze methode keert terug StepVerifier.Assertions waarop we onze beweringen kunnen toevoegen:

@Test openbare void dropElements () {StepVerifier.create (bron) .expectNext (2) .expectComplete () .verifyThenAssertThat () .hasDropped (4) .tookLessThan (Duration.ofMillis (1050)); }

4. Gegevens produceren met TestPublisher

Soms hebben we speciale gegevens nodig om de gekozen signalen te activeren.

We hebben bijvoorbeeld een heel bijzondere situatie die we willen testen.

Als alternatief kunnen we ervoor kiezen om onze eigen operator te implementeren en willen we testen hoe deze zich gedraagt.

Voor beide gevallen kunnen we gebruik maken van TestPublisher, welke stelt ons in staat om programmatisch diverse signalen te activeren:

  • volgende (T-waarde) of volgende (T-waarde, T-rest) - stuur een of meer signalen naar abonnees
  • uitstoten (T-waarde) - hetzelfde als volgende) maar roept compleet() daarna
  • compleet() - beëindigt een bron met de compleet signaal
  • fout (Throwable tr) - beëindigt een bron met een fout
  • flux () - handige methode om een TestPublisher in Flux
  • mono() - zelfde wij stroom () maar wikkelt zich naar een Mono

4.1. Een TestPublisher

Laten we een eenvoudig maken TestPublisher die een paar signalen afgeeft en dan eindigt met een uitzondering:

TestPublisher .create () .next ("Eerste", "Tweede", "Derde") .error (nieuwe RuntimeException ("Bericht"));

4.2. TestPublisher in actie

Zoals we eerder al zeiden, willen we soms triggeren een fijn gekozen signaal dat nauw aansluit bij een bepaalde situatie.

Nu is het in dit geval vooral belangrijk dat we de bron van de gegevens volledig beheersen. Om dit te bereiken kunnen we weer een beroep doen op TestPublisher.

Laten we eerst een klasse maken die gebruikmaakt van Flux als de constructorparameter om de bewerking uit te voeren getUpperCase ():

class UppercaseConverter {privé finale Flux-bron; UppercaseConverter (Flux-bron) {this.source = source; } Flux getUpperCase () {retourbron .map (String :: toUpperCase); }}

Stel dat Omvormer in hoofdletters is onze klasse met complexe logica en operators, en we moeten heel specifieke gegevens uit de bron uitgever.

We kunnen dit gemakkelijk bereiken met Testuitgever:

laatste TestPublisher testPublisher = TestPublisher.create (); UppercaseConverter uppercaseConverter = nieuwe UppercaseConverter (testPublisher.flux ()); StepVerifier.create (hoofdletterConverter.getUpperCase ()) .then (() -> testPublisher.emit ("aA", "bb", "ccc")) .expectNext ("AA", "BB", "CCC"). verifiërenComplete ();

In dit voorbeeld maken we een test Flux uitgever in de Omvormer in hoofdletters constructor parameter. Dan, onze TestPublisher zendt drie elementen uit en voltooit.

4.3. Zich misdragen TestPublisher

Aan de andere kant, we kunnen ons misdragen TestPublisher met het createNonCompliant fabrieks methode. We moeten de constructor één opsommingswaarde van doorgeven TestPublisher. Schending. Deze waarden geven aan welke delen van specificaties onze uitgever misschien over het hoofd ziet.

Laten we eens kijken naar een TestPublisher dat zal geen NullPointerException voor de nul element:

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

In aanvulling op ALLOW_NULL, we kunnen ook gebruiken TestPublisher. Schending naar:

  • REQUEST_OVERFLOW - staat bellen toe De volgende() zonder een IllegalStateException als er onvoldoende verzoeken zijn
  • CLEANUP_ON_TERMINATE - maakt het mogelijk om elk beëindigingssignaal meerdere keren achter elkaar te verzenden
  • DEFER_CANCELLATION - stelt ons in staat annuleringssignalen te negeren en door te gaan met het uitzenden van elementen

5. Conclusie

In dit artikel, we bespraken verschillende manieren om reactieve stromen uit de Lente Reactor project.

Eerst hebben we gezien hoe StepVerifier om uitgevers te testen. Vervolgens hebben we gezien hoe we het moesten gebruiken TestPublisher. Evenzo hebben we gezien hoe we te werk moesten gaan als we zich misdragen TestPublisher.

Zoals gewoonlijk is de implementatie van al onze voorbeelden te vinden in het Github-project.