How to mock Spring WebFlux WebClient? How to mock Spring WebFlux WebClient? spring spring

How to mock Spring WebFlux WebClient?


We accomplished this by providing a custom ExchangeFunction that simply returns the response we want to the WebClientBuiler:

webClient = WebClient.builder()            .exchangeFunction(clientRequest ->                     Mono.just(ClientResponse.create(HttpStatus.OK)                    .header("content-type", "application/json")                    .body("{ \"key\" : \"value\"}")                    .build())            ).build();myHttpService = new MyHttpService(webClient);Map<String, String> result = myHttpService.callService().block();// Do assertions here    

If we want to use Mokcito to verify if the call was made or reuse the WebClient accross multiple unit tests in the class, we could also mock the exchange function:

@Mockprivate ExchangeFunction exchangeFunction;@BeforeEachvoid init() {    WebClient webClient = WebClient.builder()            .exchangeFunction(exchangeFunction)            .build();    myHttpService = new MyHttpService(webClient);}@Testvoid callService() {    when(exchangeFunction.exchange(any(ClientRequest.class)))   .thenReturn(buildMockResponse());    Map<String, String> result = myHttpService.callService().block();    verify(exchangeFunction).exchange(any());    // Do assertions here}    

Note: If you get null pointer exceptions related to publishers on the when call, your IDE might have imported Mono.when instead of Mockito.when.

Sources:


With the following method it was possible to mock the WebClient with Mockito for calls like this:

webClient.get().uri(url).header(headerName, headerValue).retrieve().bodyToMono(String.class);

or

webClient.get().uri(url).headers(hs -> hs.addAll(headers));.retrieve().bodyToMono(String.class);

Mock method:

private static WebClient getWebClientMock(final String resp) {    final var mock = Mockito.mock(WebClient.class);    final var uriSpecMock = Mockito.mock(WebClient.RequestHeadersUriSpec.class);    final var headersSpecMock = Mockito.mock(WebClient.RequestHeadersSpec.class);    final var responseSpecMock = Mockito.mock(WebClient.ResponseSpec.class);    when(mock.get()).thenReturn(uriSpecMock);    when(uriSpecMock.uri(ArgumentMatchers.<String>notNull())).thenReturn(headersSpecMock);    when(headersSpecMock.header(notNull(), notNull())).thenReturn(headersSpecMock);    when(headersSpecMock.headers(notNull())).thenReturn(headersSpecMock);    when(headersSpecMock.retrieve()).thenReturn(responseSpecMock);    when(responseSpecMock.bodyToMono(ArgumentMatchers.<Class<String>>notNull()))            .thenReturn(Mono.just(resp));    return mock;}


You can use MockWebServer by the OkHttp team. Basically, the Spring team uses it for their tests too (at least how they said here). Here is an example with reference to a source:

According to Tim's blog post let's consider that we have the following service:

class ApiCaller {       private WebClient webClient;       ApiCaller(WebClient webClient) {      this.webClient = webClient;   }       Mono<SimpleResponseDto> callApi() {       return webClient.put()                       .uri("/api/resource")                       .contentType(MediaType.APPLICATION_JSON)                       .header("Authorization", "customAuth")                       .syncBody(new SimpleRequestDto())                       .retrieve()                       .bodyToMono(SimpleResponseDto.class);    }}

then the test could be designed in the following way (comparing to origin I changed the way how async chains should be tested in Reactor using StepVerifier):

class ApiCallerTest {    private final MockWebServer mockWebServer = new MockWebServer();  private final ApiCaller apiCaller = new ApiCaller(WebClient.create(mockWebServer.url("/").toString()));    @AfterEach  void tearDown() throws IOException {     mockWebServer.shutdown();  }    @Test  void call() throws InterruptedException {       mockWebServer.enqueue(new MockResponse().setResponseCode(200)                                               .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)                                               .setBody("{\"y\": \"value for y\", \"z\": 789}")      );            //Asserting response      StepVerifier.create(apiCaller.callApi())                  .assertNext(res -> {                        assertNotNull(res);                        assertEquals("value for y", res.getY());                        assertEquals("789", res.getZ());                  })                  .verifyComplete();      //Asserting request     RecordedRequest recordedRequest = mockWebServer.takeRequest();     //use method provided by MockWebServer to assert the request header     recordedRequest.getHeader("Authorization").equals("customAuth");     DocumentContext context = >JsonPath.parse(recordedRequest.getBody().inputStream());     //use JsonPath library to assert the request body     assertThat(context, isJson(allOf(            withJsonPath("$.a", is("value1")),            withJsonPath("$.b", is(123))     )));  }}