Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock Spring WebFlux WebClient?

We wrote a small Spring Boot REST application, which performs a REST request on another REST endpoint.

@RequestMapping("/api/v1") @SpringBootApplication @RestController @Slf4j public class Application {     @Autowired     private WebClient webClient;      @RequestMapping(value = "/zyx", method = POST)     @ResponseBody     XyzApiResponse zyx(@RequestBody XyzApiRequest request, @RequestHeader HttpHeaders headers)     {         webClient.post()             .uri("/api/v1/someapi")             .accept(MediaType.APPLICATION_JSON)             .contentType(MediaType.APPLICATION_JSON)             .body(BodyInserters.fromObject(request.getData()))             .exchange()             .subscribeOn(Schedulers.elastic())             .flatMap(response ->                     response.bodyToMono(XyzServiceResponse.class).map(r ->                     {                         if (r != null)                         {                             r.setStatus(response.statusCode().value());                         }                          if (!response.statusCode().is2xxSuccessful())                         {                             throw new ProcessResponseException(                                     "Bad status response code " + response.statusCode() + "!");                         }                          return r;                     }))             .subscribe(body ->             {                 // Do various things             }, throwable ->             {                 // This section handles request errors             });          return XyzApiResponse.OK;     } } 

We are new to Spring and are having trouble writing a Unit Test for this small code snippet.

Is there an elegant (reactive) way to mock the webClient itself or to start a mock server that the webClient can use as an endpoint?

like image 398
Roman Avatar asked Jul 25 '17 10:07

Roman


People also ask

How do I mock a response to WebClient?

We have two main options for mocking in our tests: Use Mockito to mimic the behavior of WebClient. Use WebClient for real, but mock the service it calls by using MockWebServer (okhttp)

Is WebClient part of Webflux?

The WebClient is part of spring-webflux module and we will add it as required dependency for Spring Reactive support. Keep in mind that Spring WebClient will be used on top of an existing asynchronous HTTP client library. We have the following options: Reactor Netty.


2 Answers

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

 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:

@Mock private ExchangeFunction exchangeFunction;  @BeforeEach void init() {     WebClient webClient = WebClient.builder()             .exchangeFunction(exchangeFunction)             .build();      myHttpService = new MyHttpService(webClient); }  @Test void 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:

like image 74
Renette Avatar answered Sep 20 '22 11:09

Renette


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; } 
like image 41
Igors Sakels Avatar answered Sep 19 '22 11:09

Igors Sakels