I have this code which uses WebClient to call a third party API.
public Mono<JsonNode> callApi(String url) {
return webClient.get()
.uri(url)
.headers(httpHeaders -> httpHeaders.set(Constants.X_API_KEY, apiKey))
.retrieve()
.onStatus(HttpStatus::is5xxServerError,
res -> {
res.bodyToMono(String.class)
.subscribe(e -> log.error(Constants.EXCEPTION_LOG, e));
return Mono.error(new RetryableException("Server error: " res.rawStatusCode()));
})
.onStatus(HttpStatus::is4xxClientError,
res -> {
res.bodyToMono(String.class)
.subscribe(e -> log.error("Exception occurred in callPartnerApi: No retries {}", e));
return Mono.error(new Exception("Exception occurred calling partner api, no retries " res.rawStatusCode()));
})
.bodyToMono(JsonNode.class);
}
I am trying to use Mockito to unit test this and so far I have this which is failing:
@Test
void testCallPartnerApi_then5xxException() {
WebClient.RequestHeadersUriSpec requestHeadersUriSpec = mock(WebClient.RequestHeadersUriSpec.class);
WebClient.RequestHeadersSpec requestHeadersSpec = mock(WebClient.RequestHeadersSpec.class);
WebClient.ResponseSpec responseSpec = mock(WebClient.ResponseSpec.class);
when(webClient.get()).thenReturn(requestHeadersUriSpec);
when(requestHeadersUriSpec.uri(anyString())).thenReturn(requestHeadersSpec);
when(requestHeadersSpec.headers(any())).thenReturn(requestHeadersSpec);
when(requestHeadersSpec.retrieve()).then(invocationOnMock -> Mono.error(new RetryableException("Server error: 500")));
when(responseSpec.onStatus(argThat(x -> x.test(HttpStatus.INTERNAL_SERVER_ERROR)), any())).thenAnswer(invocation -> Mono.error(new RetryableException("Server error: 500")));
StepVerifier.create(partnerService.callPartnerApi("/test"))
.expectError()
.verify();
}
The error I get from this test is this:
java.lang.ClassCastException: class reactor.core.publisher.MonoError cannot be cast to class org.springframework.web.reactive.function.client.WebClient$ResponseSpec
Is using a library like WireMock or MockServerTest the only way to test these scenarios?
CodePudding user response:
Unit testing WebClient has very low ROI. I would recommend looking at WireMock that provides very good API for testing web clients. Here are some examples
stubFor(get(url)
.withHeader(HttpHeaders.ACCEPT, containing(MediaType.APPLICATION_JSON_VALUE))
.willReturn(aResponse()
.withStatus(200)
.withBody("{}")
)
);
StepVerifier.create(service.callApi())
.expectNextCount(1)
.verifyComplete();
You could easily test both positive and negative scenarios by providing different stubs
stubFor(get(url)
.withHeader(HttpHeaders.ACCEPT, containing(MediaType.APPLICATION_JSON_VALUE))
.willReturn(aResponse()
.withStatus(500)
)
);
In addition, you could test retry logic using Scenarios or even simulate timeouts using Delays.
To initialize it you could use @AutoConfigureWireMock
provided by org.springframework.cloud:spring-cloud-contract-wiremock
or, as an alternative, you can add direct dependency to WireMock
and initialize it explicitly.
CodePudding user response:
requestHeadersSpec.retrieve() return ResponseSpec, but you are trying to return Mono.error by mockito instead. You need to return result of valid type, something that can be inherited from ResponseSpec. You may return mock of ResponseSpec or something like new DefaultResponseSpec(Mono.error(new RetryableException("Server error: 500")))
static class DefaultResponseSpec implements ResponseSpec {
private final Mono<ClientResponse> responseMono;
DefaultResponseSpec(Mono<ClientResponse> responseMono) {
this.responseMono = responseMono;
}