Correct testing pattern in Project Reactor block() vs StepVerifier
Asked Answered
S

2

5

Recently I noticed that my team follows two approaches on how to write tests in Reactor. First one is with help of .block() method. And it looks something like that:

@Test
void set_entity_version() {
    Entity entity = entityRepo.findById(ID)
                              .block();
    assertNotNull(entity);
    assertFalse(entity.isV2());

    entityService.setV2(ID)
                 .block();

    Entity entity = entityRepo.findById(ID)
                              .block();

    assertNotNull(entity);
    assertTrue(entity.isV2());
}

And the second one is about using of StepVerifier. And it looks something like that:

@Test
void set_entity_version() {

    StepVerifier.create(entityRepo.findById(ID))
                .assertNext(entity -> {
                    assertNotNull(entity);
                    assertFalse(entity.isV2());
                })
                .verifyComplete();
            
    StepVerifier.create(entityService.setV2(ID)
                                     .then(entityRepo.findById(ID)))
                .assertNext(entity -> {
                    assertNotNull(entity);
                    assertTrue(entity.isV2());
                })
                .verifyComplete();
}

In my humble opinion, the second approach looks more reactive I would say. Moreover, official docs are very clear on that:

A StepVerifier provides a declarative way of creating a verifiable script for an async Publisher sequence, by expressing expectations about the events that will happen upon subscription.

Still, I'm really curious, what way should be encouraged to use as the main road for doing testing in Reactor. Should .block() method be abandoned completly or it could be useful in some cases? If yes, what such cases are?

Thanks!

Stoop answered 2/7, 2020 at 13:10 Comment(4)
StepVerifier allows you to verify individual events in the chain, whereas block() would only ever allow you to verify the outcome at the end. I can't think of any reason to ever use block() over StepVerifier but can think of plenty not to.Nomadic
In case of Mono I don't really see any advantage of StepVerifier.Allpowerful
@MartinTarjányi Tons of advantages. You can verify whether it has subscribes/errors/completes/onNext events. You can verify an event occurs within a time limit. Verify if elements have been correctly discarded via a filter. Verify various aspects of the subscriberContext + lots more. Block() literally just gives you an imperative value at the end. Gives no way of testing what has actually happened within the chain.Nomadic
These are quite useful for a Flux, not so much for a Mono. Most of the cases, I don't need such low-level asserts.Allpowerful
S
5

from https://medium.com/swlh/stepverifier-vs-block-in-reactor-ca754b12846b

There are pros and cons of both block() and StepVerifier testing patterns. Hence, it is necessary to define a pattern or set of rules which can guide us on how to use StepVerifier and block().

In order to decide which patterns to use, we can try to answer the following questions which will provide a clear expectation from the tests we are going to write:

  • Are we trying to test the reactive aspect of the code or just the output of the code?
  • In which of the patterns we find clarity based on the 3 A’s of testing i.e Arrange, Act, and Assert, in order to make the test understandable?
  • What are the limitations of the block() API over StepVerifier in testing reactive code? Which API is more fluent for writing tests in case of Exception?

If you try answering all these questions above, you will find the answers to “what” and “where”. So, just give it a thought before reading the following answers:

  • block() tests the output of the code and not the reactive aspect. In such a case where we are concerned about testing the output of the code, rather than the reactive aspect of the code we can use a block() instead of StepVerifier as it is easy to write and the tests
    are more readable.
  • The assertion library for a block() pattern is better organised in terms of 3 A’s pattern i.e Arrange, Act, and Assert than StepVerifier. In StepVerfier while testing a method call for a mock class or even while testing a Mono output one has to write expectation in the form of chained methods, unlike assert which in my opinion decreases the readability of the tests. Also, if you forget to write the terminal step i.e verify() in case of StepVerifier, the code will not get executed and the test will go green. So, the developer has to be very careful about calling verify at end of the chain.
  • There are some aspects of reactive code that can not be tested by using block() API. In such cases, one should use StepVerifier when we are testing a Flux of data or subscription delays or subscriptions on different Schedulers, etc, where the developer is bound to use StepVerifier.
  • To verify exception by using block() API you need to use assertThatThrownBy API in assertions library that catches the exception. With the use of an assertion API, error message and instance of the exception can be asserted. StepVerifier also provides assertions on exception by expectError() API and supports the
    assertion of the element before errors are thrown in a Flux of elements that can not be achieved by block(). So, for the assertion of exception, StepVerifier is better than a block() as it can assert
    both Mono/Flux.
Stoop answered 27/2, 2021 at 20:51 Comment(0)
A
2

You should use StepVerifier. It allows more options:

  • Verify that you expect n element in a flux
  • Verify that the flux/mono complete
  • Verify that an error is expected
  • Verify that a sequence is expected n element followed by an error (impossible to test with .block())

From the official doc:

public <T> Flux<T> appendBoomError(Flux<T> source) {
  return source.concatWith(Mono.error(new IllegalArgumentException("boom")));
}

@Test
public void testAppendBoomError() {
  Flux<String> source = Flux.just("thing1", "thing2"); 

  StepVerifier.create( 
    appendBoomError(source)) 
    .expectNext("thing1") 
    .expectNext("thing2")
    .expectErrorMessage("boom") 
    .verify(); 
}

Agreeable answered 2/7, 2020 at 13:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.