Testing with StepVerifier in Project Reactor

In this lesson, we will cover testing with StepVerifier in Project Reactor. We will create a Publisher and then examine how it behaves. Let’s dive in.

How to test Flux and Mono?

When creating a Publisher (Flux or Mono), we would like to know how it behaves when someone subscribes to it. Well, that can be easily done with the StepVerifier class.

Using this class, we can subscribe to the Publisher, define a step-by-step test and perform some assertions. If any event is triggered that doesn’t match the current expectation, the StepVerifier will produce an AssertionError.

To be able to use the StepVerifier, we need to have the following Maven dependency in the pom.xml file:

<dependency>
    <groupid>io.projectreactor</groupid>
    <artifactid>reactor-test</artifactid>
    <version>3.1.0.RELEASE</version>
    <scope>test</scope>
</dependency>

Unit Testing with StepVerifier

In the first example, let’s create a Flux of integers and specify what we expect (what elements to receive and in what order) when we subscribe.

Example

class ReactiveJavaTutorialTest {

  @Test
  public void testFlux() {

    //Create a Flux
    Flux <Integer> fluxToTest = Flux.just(1, 2, 3, 4, 5);

    // Create a test
    StepVerifier.create(fluxToTest)
            .expectNext(1)
            .expectNext(2)
            .expectNext(3)
            .expectNext(4)
            .expectNext(5)
            .expectComplete() // we expect Flux to complete after sending the number 5 which is the last element
            .verify();

  }
}
Test passed!
 
Note: We need to put verify() or other variations at the end of the chain. Otherwise, the StepVerifier won’t subscribe to our Publisher.
 
Let’s see what happens if we expect a different order of elements:
 
class ReactiveJavaTutorialTest {

  @Test
  public void testFlux() {

    //Create a Flux
    Flux<Integer> fluxToTest = Flux.just(1, 2, 3);

    // Create a test
    StepVerifier.create(fluxToTest)
            .expectNext(1)
            .expectNext(3)
            .expectNext(2)
            .expectComplete()
            .verify();

  }
}
Output: Test failed! java.lang.AssertionError: expectation “expectNext(3)” failed (expected value: 3; actual value: 2)
 
We got an AssertionError because we expected the second element to be 3 instead of 2.
 
Let’s write an example where we have some operators in a reactive chain:
class ReactiveJavaTutorialTest {

  @Test
  public void testFlux() {

    Flux<String> fluxToTest = Flux.just("Jessica", "John", "Tomas", "Melissa", "Steve", "Megan", "Monica", "Henry")
            .filter(name -> name.length() <= 5)
            .map(String::toUpperCase);

    StepVerifier.create(fluxToTest)
            .expectNext("JOHN")
            .expectNext("TOMAS")
            .expectNextMatches(name -> name.startsWith("ST"))
            .expectNext("MEGAN")
            .expectNext("HENRY")
            .expectComplete()
            .verify();

  }
}
Test passed!
 
Here, we used the expectNextMatches() for a custom match.

Testing Exceptions with StepVerifier

Let’s see now how we can set the StepVerifier to expect an exception from the Flux.

For this example, we will create a Flux of integers and concatenate it with Mono that terminates immediately due to exception.

Example

class ReactiveJavaTutorialTest {

  @Test
  public void testFlux() {

    Flux<Integer> fluxToTest = Flux.just(1, 2, 3, 4)
            .concatWith(Mono.error(new ArithmeticException("Error occurred!")));

    StepVerifier.create(fluxToTest)
            .expectNext(1)
            .expectNext(2)
            .expectNext(3)
            .expectNext(4)
            .expectErrorMatches(throwable -> throwable instanceof ArithmeticException &&
                    throwable.getMessage().equals("Error occurred!"))
            .verify();

  }
}
Test passed!

Testing Time Consuming Publishers

We can have Publishers that have a delay between each element that emits. 

For example, if we have a Mono that will emit an element with some delay, then the StepVerifier will wait for that element until it gets it.

Example

class ReactiveJavaTutorialTest {

  @Test
  public void testFlux() {

    Mono<String> monoWithDelay = Mono.just("Reactive")
            .delayElement(Duration.ofSeconds(5));

    long start = System.currentTimeMillis() / 1000;
    StepVerifier.create(monoWithDelay)
            .expectNext("Reactive")
            .expectComplete()
            .verify();

    System.out.println("Time taken for the StepVerifier: " + (System.currentTimeMillis() / 1000 - start) + " seconds");

  }
}
Test passed! Time taken for the StepVerifier: 5 seconds
 
We can test this Mono by setting the time for the StepVerifier to wait for the emitted element. 

Let’s expect for this Mono to emit element after only 3 seconds and see what happens:

Example

class ReactiveJavaTutorialTest {

  @Test
  public void testFlux() {

    Mono<String> monoWithDelay = Mono.just("Reactive")
            .delayElement(Duration.ofSeconds(5));

    StepVerifier.create(monoWithDelay)
            .expectNext("Reactive")
            .expectComplete()
            .verify(Duration.ofSeconds(3)); // will wait 3 seconds

  }
}
Test failed! java.lang.AssertionError: VerifySubscriber timed out on reactor.core.publisher.MonoDelayElement$DelayElementSubscriber@2cc75074
 
This kind of testing of the time-based Publishers is really useful when dealing with some external service (REST API), and we want to test its reliability.
 
That would be all regarding Testing Reactive Streams with StepVerifier in Project Reactor.
 
Happy coding!

Leave a Reply

Your email address will not be published. Required fields are marked *