Search code examples
javaspring-bootunit-testingjunitmockito

Why this java WebClient mock is not working


My server sends a request via WebClient and the code is below:

public String getResponse(String requestBody){
...
    WebClient.RequestHeadersSpec<?> request =
        client.post().body(BodyInserters.fromValue(requestBody));

    String resp =
        request.retrieve().bodyToMono(String.class)
            .doOnError(
                WebClientResponseException.class,
                err -> {
                  // do something
                })
            .block();

return resp;
}

I wrote a unit test for it and want to mock the WebClient so that I can receive the expected response:

when(webClientMock.post()).thenReturn(requestBodyUriMock);
when(requestBodyUriMock.body(BodyInserters.fromValue(requestBody))).thenReturn(requestHeadersMock);
when(requestHeadersMock.retrieve()).thenReturn(responseMock);
when(responseMock.bodyToMono(String.class)).thenReturn(Mono.just("response"));

String response = someServiceSpy.getResponse(requestBody);

assertEquals(Mono.just("response"), response);

However, the result is not the "response" but a html file. I think I made a mistake somewhere but I don't know how to fix it.


Solution

  • I have figured out the solution that mocks the WebClient directly instead of putting the build logic into a new method to mock it. I wrote my solution here in case someone else needs it future:

    Let me put the code example here:

        final WebClient client =
            WebClient.builder()
                .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(someValue))
                .clientConnector(new ReactorClientHttpConnector(HttpClient.create(someProvider)))
                .baseUrl(someUrl)
                .defaultHeader(contentType, TEXT_XML_VALUE)
                .build();
    
        final WebClient.RequestHeadersSpec<?> request =
            client.post().body(BodyInserters.fromValue(reqBody));
    

    First, we must mock the static method builder() of WebClient. If this method is not mocked, mockito can't edit the behavior of this method, and the mocked WebClient would not be used. I found this from the answer to this StackOverflow question; you can read it for more details.: How to mock Spring WebClient and builder

    After mocked the builder() with the method provided by the above anwser, you will get a mocked WebClient, it's something like:

    when(webClientBuilder.build()).thenReturn(webClientMock);
    

    Then you can start to finish the rest of the work. In my sample code, the client will invoke post() and body(), so write the following:

        when(requestBodyUriMock.body(any())).thenReturn(requestHeadersMock);
        when(requestHeadersMock.retrieve()).thenReturn(responseMock);
        when(responseMock.bodyToMono(String.class)).thenReturn(Mono.just(expectedResponse));
    

    My unit test returned the NPE at the beginning and it because I used

    when(requestBodyUriMock.body(BodyInserters.fromValue(requestBody))).thenReturn(requestHeadersMock);
    

    instead of

    when(requestBodyUriMock.body(any())).thenReturn(requestHeadersMock);
    

    I think it is because the code not "think" the requestBodyUriMock is using the BodyInserters.fromValue(requestBody for some reasons that I haven't know yet. After I changed it to any(), it worked.