I am trying to make a REST call using WebClient, however I am not able to pass the request body. it is showing error as - the method syncBody(body) is undefined for the type capture #1-of?
public static String getResult(Body body) {
WebClient webClient = WebClient.builder()
.defaultHeader(HttpHeaders.CONTENT_TYPE,MediaType.APPLICATION_JSON_VALUE)
.build();
String result= webClient.get()
.uri(URL)
.syncBody(body)
.retrieve()
.bodyToMono(String.class)
.block();
}
it is showing error as - the method syncBody(body) is undefined for the type capture #1-of?
I may be missing something, but I do not think you can use WebClient. Actually, in spring boot 2.0.5 this is not even compiling.
What happens is that when you invoke .get() you get an instance of RequestHeadersUriSpec that does not have support for body or syncBody methods, while when invoking post (or put, etc) you get an instance of RequestBodyUriSpec that does
Related
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.
It seems the client referenced in your getResponse method is not set to the mock you have created (webClientMock) in your test.
If you are creating this client object in your getResponse method, I would suggest that you create it using a method that you could mock. Something like
WebClient buildWebClient() {
// build your webclient using the WebClientBuilder
}
You may want to throw a comment and or a #VisibleForTesting annotation on there so it is clear this method exists in order to make testing easier.
Then you can stub this method in your someServiceSpy:
Mockito.doReturn(mockWebClient).when(someServiceSpy).buildWebClient();
This will ensure that your mockWebClient is used in your getResponse method in your test.
Additionally, it seems as though your existing code needs a slight edit.
when(requestBodyUriMock.body(BodyInserters.fromValue(requestBody))).thenReturn(requestHeadersMock);
Should be
when(requestBodyUriMock.body(eq(BodyInserters.fromValue(requestBody)))).thenReturn(requestHeadersMock);
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.
Is it possible to use stubFor here?
If yes, how to do it?
If not, how to test this code?
public Mono<Void> trelloCallback(Type payload) {
webClient.post()
.uri(URL)
.bodyValue(payload)
.headers(httpHeaders -> httpHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE))
.retrieve()
.bodyToMono(Void.class)
.subscribe();
return Mono.empty();
}
Info: If we add .subscribe to the code, it runs in the background.
I test the result, and I can see WireMock.stubFor is working with the subscribe call.
For more explanation,
We first create a WireMock.stubFor for some endpoint call.
Then we make an actual call using the webTestClient to do integration test. And then verify the result using
verify(exactly(1), postRequestedFor(urlEqualTo(SOME_URL)));
// For SOME_URL create a `stubFor`
My goal is to get the HttpStatus from a Spring WebClient request. All other information of the request is irrelevant.
My old way of doing this was:
WebClient.builder().baseUrl("url").build()
.get().exchange().map(ClientResponse::statusCode).block();
With Spring Boot 2.4.x/Spring 5.3, the exchange method of WebClient is deprecated in favor of retrieve.
With the help of https://stackoverflow.com/a/65791800 i managed to create the following solution.
WebClient.builder().baseUrl("url").build()
.get().retrieve()
.toEntity(String.class)
.flatMap(entity -> Mono.just(entity.getStatusCode()))
.block();
However, this looks like a very "hacky" way and and does not work when 4xx or 5xx responses are received (In this case the result is null).
This problem can be solved with the method exchangeToMono. This results into the following snippet.
WebClient.builder().baseUrl("url").build()
.get().exchangeToMono(response -> Mono.just(response.statusCode()))
.block();
The usage of the retrieve method can be improved in the following way, still not very clean.
WebClient.builder().baseUrl("url").build()
.get().retrieve()
.onStatus(httpStatus -> true, clientResponse -> {
//Set variable to return here
status.set(clientResponse.statusCode());
return null;
})
.toBodilessEntity().block();
I want to use WebClient response in the next API call. So before calling the next api some of the fields from the response extract and use them in the next api call. There is a way to block WebClient response and use it. But is there any way to do it without blocking? So my code looks like this
response = getUserByWebClient1(); // web client call 1
extract id from response
getRolesByUserId(id); // webclient call 2
This is not specific to WebClient, but a general concept with reactive types, here Reactor Flux and Mono.
You can use the flatMap operator to achieve just that.
// given UserService with a method "Mono<User> getCurrentUser()"
// and RolesService with a method "Mono<RoleDetails> getRolesForUser(Long userId)"
Mono<RolesDetails> roles = userService.getCurrentUser()
.flatMap(user -> rolesService.getRolesForUser(user.getId());
I am using spring framework reactive webclient to make a call like below
webClient.post()
.uri("/v/score/$model")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(gson.toJson(request))
.accept(MediaType.APPLICATION_JSON)
.header("Client-Id", clientId)
.awaitExchange()
.awaitBody<ScoringResponse>()
which is working fine. Now I wan to pass the request as a protobuff object instead of json. How can I do that ?
Set the media type to application/octet-stream and pass your proto model in a byte array form by using the .toByteArray() method. On the receiving end you can use the static method {proto generated class}.parseFrom({your bytes come here}) to rebuild the proto object.
Do not forget the POST method request is basically a body content ;)