Pact consumer test does not successfully mock the spring webclient request using the created pact - spring-webclient

I am new to Pact Contract testing and I am trying to create a Pact consumer test to validate a method that calls an api with get request. The api request is made using Spring Webclient.
I am not able to create the webclient object by just providing the Pact mockserver eg.
WebClient webClient = WebClient.builder().baseUrl(mockServer.getUrl()).build();
I am getting the exception java.lang.IllegalStateException: No suitable default ClientHttpConnector found. The explanation I get on the internet for that , is to include reactor-netty-http and I was able to get past this issue when i included that in the POM. But I don't think that is the right solution here because I need the mockserver to respond to the webclient request and it is not. Has anyone dealt with this issue before or am I doing this wrong?
Here is the code snippet:
public RequestResponsePact pactMethod(PactDslWithProvider builder) {
Map<String, String> headers = new HashMap<>();
headers.put(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
return builder.given("Consumer request")
.uponReceiving(" getResource call")
.path("/path")
.method("GET")
.willRespondWith()
.status(200)
.headers(headers)
.body(RESPONSE_JSON).toPact();
}
#Test
#PactTestFor(pactMethod = "pactMethod", port = "9999")
public void consumerTest(MockServer mockServer) {
WebClient webClient = WebClient.builder().baseUrl(mockServer.getUrl()).build();
ConsumerServiceClient consumerServiceClient = new ConsumerServiceClient(webClient);
Mono<Data> data = consumerServiceClient.getData();
StepVerifier.create(data)
.assertNext(resp -> {
try {
Value value = resp.getValue();
Assertions.assertFalse( value.isEmpty());
} catch (Exception e) {
log.error("Unable to convert response to Value", e);
Assertions.fail();
}
}).expectComplete()
.verify();
}
The webclient call:
webClient.get()
.uri("/path")
.retrieve()
.onStatus(status -> status == HttpStatus.NOT_FOUND,
res -> Mono.error(new RunTimeException()))
.bodyToMono(clazz);

Related

Spring Webflux Webclient timesout intermittently

I am getting intermittent ReadTimeOut from netty with the below error:
The connection observed an error","logger_name":"reactor.netty.http.client.HttpClientConnect","thread_name":"reactor-http-epoll-3","level":"WARN","level_value":30000,"stack_trace":"io.netty.handler.timeout.ReadTimeoutException: null
One observation we made is this particular endpoint for which we are getting this issue is a POST with no request body. I am now sending a dummy json in body now which the downstream system ignores and now I don't see this error anymore at all.
Below is my code:
protected <T, S Mono<S sendMonoRequest (HttpMethod method,
HttpHeaders headers,
T requestBody,
URI uri, Class < S responseClass)
throws ApiException, IOException {
log.info("Calling {} {} {} {}", method.toString(), uri.toString(), headers.toString(),
mapper.writeValueAsString(requestBody));
WebClient.RequestBodySpec requestBodySpec = getWebClient().method(method).uri(uri);
headers.keySet().stream().forEach(headerKey -> headers.get(headerKey).stream().
forEach(headerValue -> requestBodySpec.header(headerKey, headerValue)));
return requestBodySpec
.body(BodyInserters.fromObject(requestBody != null ? requestBody : ""))
.retrieve()
.onStatus(HttpStatus::is4xxClientError, this::doOn4xxError)
.onStatus(HttpStatus::is5xxServerError, this::doOn5xxError)
.onStatus(HttpStatus::isError, this::doOnError)
.bodyToMono(responseClass);
}
protected WebClient getWebClient () {
HttpClient httpClient = HttpClient.create().tcpConfiguration(
client -> client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,
20000).doOnConnected(conn - conn
.addHandlerLast(new ReadTimeoutHandler(20)).addHandlerLast(new WriteTimeoutHandler(20))));
ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
return WebClient.builder().clientConnector(connector)
.filter(logResponse())
.build();
}
To resolve the intemrittent timeouts, I have to send a dummy pojo to sendMonoRequest() for request body. Any ideas ?

How to call a microservice to fetch data in spring webflux

I want to call a microservice from another service using webclient in spring flux. But, I am not able to write the code properly. Can you please suggest how to call another service. Please find my code as below.
I need to call the below service
public Mono<ServerResponse> load(ServerRequest res){
String c1name = res.pathVariable("cust");
String c2name = res.queryParam("cl").orElse("");
String oname = res.queryParam("ol").orElse("");
return res.body()
}
public Mono<ResponseEntity<Void>> ftpFileSend(MultipartFile fileData, String cust, MultiValueMap<String,String) qpar {
MultiValueMap<String,String> qpar=new LinkedMultiValueMap<String,String>();
qpar.add("name","spring");
MultiValueMap<String,Object> body=new LinkedMultiValueMap<String,Object>();
String url="http://localhost:8088/"+ cust+"/load";
try {
body.add("file", fileData.getBytes());
} catch (IOException e) {
return Mono.error(e); // <-- note how to create an error signal
}
return webClient
.post()
.uri(uriBuilder -> uriBuilder.path(url).queryParams(qpar).build() )
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(BodyInserters.fromMultipartData(body))
.retrieve()
.toBodilessEntity();
}
Hmm it would be great if you have provided some error logs or so. Anyway if you want to create a multipart body there is a builder, MultipartBodyBuilder (in org.springframework.http.client.MultipartBodyBuilder).
Example usage is as follows,
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("file", new MultipartFileResource(fileData));
MultiValueMap<String, HttpEntity<?>> multipartBody = builder.build();
Then use this multipartBody in webClient call.
return webClient
...
.body(BodyInserters.fromMultipartData(multipartBody))
.retrieve()
.toBodilessEntity();

WireMock gives a null response body when mocking an API call using spring boot resttemplate

I'm facing a weird issue with Wiremock. The code below returns a null response body. Any insight will be very much appreciated.
Stub in my test:
WireMock.stubFor(post(urlPathEqualTo("http://localhost:8080/mapper"))
.willReturn(WireMock.aResponse()
.withStatus(HttpStatus.OK.value())
.withBody(asJson("ct/slotting-response/create_sample_response1.json"))
.withHeader("Content-Type","application/json;charset=UTF-8")));
Actual API call using spring boot resttemplate:
public ResponseEntity<SampleResponse> getsampleValue(final SampleRequest request, RequestHeader requestHeader) throws SlottingException {
try {
log.info("Sending request[payload={}]", request);
final HttpHeaders headers = getRequestHeader(requestHeader);
HttpEntity<?> entity = new HttpEntity<>(request, headers);
final ResponseEntity<SampleResponse> response =
restTemplate.postForEntity("http://localhost:8080/mapper",
entity, SampleResponse.class);
log.info("Sample response {}", response); // response.getBody() gives null
if (HttpStatus.OK.equals(response.getStatusCode())) {
log.info("Sample allocated successfully.");
}
else {
throw new SampleException("failed");
}
return response;
} catch (Exception e) {
throw new SampleException("Failed", e);
}
}
Can someone please point out any obvious mistakes you see in the Wiremock stub?

Reactive Spring WebClient calls

I am trying to understand WebFlux but having some trouble with Webclient calls. I do not see
this line System.out.println("customerId = " + customerId); executes it seems like it does not call the endpoint.
But if I subscribe to webclient with .subscribe(customer -> {}); then I can see this line System.out.println("customerId = " + customerId); works
on the endpoint side. I dont understand why I have to subscribe to Mono call, or do I have to ? Thanks
#GetMapping("/customer/{customerId}")
#ResponseStatus(HttpStatus.ACCEPTED)
public Mono<Void> getCustomer(#PathVariable("customerId") int customerId) {
WebClient webClient = WebClient.builder().baseUrl("http://localhost:8080").build();
webClient.get()
.uri("/client/customer/{customerId}",customerId)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Customer.class);//here do I have to subscribe to actually activate to call?
return null;
}
#GET
#Path("/customer/{customerId}")
#Produces(MediaType.APPLICATION_JSON)
public Customer getCustomer(#PathParam("customerId") int customerId) throws InterruptedException {
System.out.println("customerId = " + customerId); // I do not see the call comes thru if I dont subscribe to flux call.
return new Customer(customerId,"custName");
}
If you want to return the reactive type from your WebClient, you have to return it from your controller method like:
#GetMapping("/customer/{customerId}")
#ResponseStatus(HttpStatus.ACCEPTED)
public Mono<Customer> getCustomer(#PathVariable("customerId") int customerId) {
WebClient webClient = WebClient.builder().baseUrl("http://localhost:8080").build();
return webClient.get()
.uri("/client/customer/{customerId}",customerId)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Customer.class);
}
You can also return a Customer from your endpoint and block and wait for the result of your WebClient and leaving the reactive ecosystem like:
#GetMapping("/customer/{customerId}")
#ResponseStatus(HttpStatus.ACCEPTED)
public Customer getCustomer(#PathVariable("customerId") int customerId) {
WebClient webClient = WebClient.builder().baseUrl("http://localhost:8080").build();
return webClient.get()
.uri("/client/customer/{customerId}",customerId)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Customer.class)
.block()
}
If you are looking at a general introduction for Spring's WebClient, take a look at this tutorial

How to print responseBody with java reactor webclient before deserialization

We are planing to migrate from spring Rest-Template to Reactor-webclient.
With Rest-template we have written custom logging interceptors where we were printing request and response with uniqueId, before desrialization.
Now weblient provide filters, but in filters I can't access responseBody to log it.
We have some third party APIs where they send strings in case of error and some objects in case of success. In this case I can't wait to log response after deserialization, because it will break and we will not be able to log what response we got.
You can try creating a wrapper WebClient which will first log the response and then will deserialize.
The success response will fall on doOnSuccess and the error will fall on onErrorResume.
public <T> Mono<T> get(String url, Map<String, String> headersMap, Class<T> type) {
Mono<T> responseMono = client.get().uri(url).headers((h) -> headersMap.forEach(h::set)).retrieve()
.bodyToMono(type);
return responseMono.doOnSuccess(response -> log.debug("REST GET call to {} is successfull and response is {}",
url,response).onErrorResume(err -> {
log.error("Exception occurred calling get({}): {}", url,
err.getMessage());
return Mono.error(err);
});
}
Here is some sudo code for something I use to test with:
WebClient client = WebClient.create("www.google.com");
ObjectMapper mapper = new ObjectMapper();
client.get()
.retrieve()
.bodyToMono(String.class)
.map(rawBody -> {
try {
return mapper.readValue(rawBody, Response.class);
} catch (JsonProcessingException e) {
throw new RuntimeException("Cannot deserialize string: " + rawBody);
}
});

Resources