Search code examples
spring-bootunit-testingjunitspring-webflux

Webflux unit testing: call is not going inside .map and .flatMap


I am writing unit testing for function calculate in webflux

@Override
    public Mono<SearchSessionAndETAResponse> calculate(DTO sessionRequest) throws ExecutionException, InterruptedException {
        List<GeospatialData> dataFromRedis =  getDataFromRedis(sessionRequest.getSessionId());
        return Mono.just(dataFromRedis)
                .map(data -> {
                    System.out.println("data::"+data);
                    return "entityIdentifiers";
                })
                .flatMap(entityIdentifiers -> {
                    System.out.println("entityIdentifiers::"+entityIdentifiers);
                    return etaService.entitiesEta("entityIdentifiers");
                })
                .map(wayPointRoute -> {
                    return searchSessionAndETAResponse;
                });
    }
    

below is the test case

@ExtendWith(MockitoExtension.class)
public class BroadCastingServiceImplTest {

    @InjectMocks
    private BroadCastingServiceImpl broadCastingService;
    @Mock
    private EtaService etaService;

   @BeforeEach
    void setUp() {
        closeable = MockitoAnnotations.openMocks(this);
    }

    @AfterEach
    void tearDown() throws Exception {
        closeable.close();
    }

@Test
    @DisplayName("Given a valid request, when SearchSession and calculate eta is called, it should search Broadcasting session and return eta")
    void givenValidRequest_whenSearchSessionAndCalculateEta_shouldReturnBroadCastingResponseDTO() throws ExecutionException, InterruptedException, IOException {
        // GIVEN
        UUID sessionId = UUID.randomUUID();
        DTO searchSessionAndCalculateETA = new DTO();
        searchSessionAndCalculateETA.setSessionId(sessionId.toString());
        searchSessionAndCalculateETA.setWaypoints(List.of("waypointsDTO", "waypointsDTO2"));
        given(etaService.entitiesEta("entitiesEtaRequestDTO")).willReturn(Mono.just(List.of("entitiesEtaResponseDTO")));

        // WHEN
        Mono<SearchSessionAndETAResponse> result = broadCastingService.searchSessionAndCalculateETA(searchSessionAndCalculateETA);
    }
}

The problem:

when i run test case the call does not go inside .map, .flatMap and .map even though dataFromRedis is not null. Is there anything i am missing ?


Solution

  • In your test you just create the Mono but you don't actually "run" it, more precisely you do not subscribe to it.

    Working with reactive code has two phases:

    1. First you write the specification of the reactive stream, which tells what to do, but doesn't start the process. A Mono is only a fancy complex memory structure with a lot of lambda functions telling what will be done when it will be done.

    2. Then you subscribe to the Mono - only this starts the whole process and starts calling all the lambdas. Only then the "call is going inside .map and .flatMap" to paraphrase your words :)

    Read some tutorial, e.g. this Baeldung's Intro To Reactor Core.

    If you just use Mono in simple Spring applications to handle REST endpoint, you may easily miss this because your @RestController just returns the Mono and the rest (pun intended) is handled by Spring. Spring subscribes to the Mono you returned from the controller methods and handles that the result is passed to the called via HTTP.

    In unit tests, you can do it very simply just by calling its block() method, or more properly by using StepVerifier:

    result.as(StepVerifier::create)
        .expectNext(objectToExpect)
        .verifyComplete();
    

    Just using the block() method works more like a workaround. StepVerifier is the proper way of unit testing the reactive code.

    Be careful not to just use the subscribe() method, because the test may finish before the subscription has time to perform and you will get unpredictable results.