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.
Related
I could find several questions regarding mocking a WebClient object. But I still have problems when doing a post with a body and having multiple header values. I'm just using Mockito.
public Boolean addNote(AlarmModel model) {
ServiceDTO dto = mapper(model);
return webClient.post()
.uri("/service/api/addNotes")
.headers(getHttpHeaders(dto.getHeader()))
.accept(MediaType.APPLICATION_JSON)
.body(Mono.just(dto.getBody()), ServiceBodyDTO.class)
.retrieve()
.onStatus(HttpStatus::is5xxServerError, this::handleStatusCodeError)
.onStatus(HttpStatus::is4xxClientError, this::handleStatusCodeError)
.bodyToMono(Boolean.class)
.block();
}
And this is how I am mocking the behavior of the post method.
when(webClientMock.post()).thenReturn(requestBodyUriMock);
when(requestBodyUriMock.uri(anyString())).thenReturn(requestBodyMock);
when(requestHeadersMock.headers(any())).thenReturn(requestHeadersMock);
when(requestBodyMock.accept(any())).thenReturn(requestBodyMock);
when(requestBodyMock.contentType(any())).thenReturn(requestBodyMock);
when(requestBodyMock.bodyValue(any())).thenReturn(requestHeadersMock);
when(requestHeadersMock.retrieve()).thenReturn(responseMock);
when(responseMock.bodyToMono(Boolean.class))
.thenReturn(Mono.just(true));
But, when I execute this test case, it fails at the line having retrieve() And the exception is java.lang.NullPointerException
Did I miss anything here? TIA.
Agree with #Martin's comment that unit testing webclient has very low ROI.
I would recommend WireMock which provides very good API for testing web clients. Here are some examples
stubFor(post("/service/api/addNotes")
.withHeader(HttpHeaders.ACCEPT, containing(MediaType.APPLICATION_JSON_VALUE))
.willReturn(aResponse()
.withStatus(200)
.withBody("true")
)
);
StepVerifier.create(service.addNote(note))
.expectNextCount(1)
.verifyComplete();
You could easily test both positive and negative scenarios by providing different stubs
stubFor(post("/service/api/addNotes")
.withHeader(HttpHeaders.ACCEPT, containing(MediaType.APPLICATION_JSON_VALUE))
.willReturn(aResponse()
.withStatus(500)
)
);
test retry logic using Scenarios or even simulate timeouts using Delays
ps
not sure why you are using block but my example is using StepVerifier and assumes that addNote returns Mono<Boolean>
This is what works for me, not too different from yours. You might want to compare and feedback :)
#Mock
private WebClient webClient;
#Mock
WebClient.RequestBodyUriSpec requestBodyUriSpec;
#SuppressWarnings("rawtypes")
#Mock
WebClient.RequestHeadersSpec requestHeadersSpec;
#Mock
WebClient.RequestBodySpec requestBodySpec;
#Mock
WebClient.ResponseSpec responseSpec;
given(getWebClient()).willReturn(webClient);
given(webClient.post()).willReturn(requestBodyUriSpec);
given(requestBodyUriSpec.uri(anyString())).willReturn(requestBodySpec);
given(requestBodySpec.headers(any())).willReturn(requestBodySpec);
given(requestBodySpec.contentType(any())).willReturn(requestBodySpec);
given(requestBodySpec.accept(any())).willReturn(requestBodySpec);
given(requestBodySpec.bodyValue(any())).willReturn(requestHeadersSpec);
given(requestHeadersSpec.retrieve()).willReturn(responseSpec);
I'm new to Java programming and I have the following snippet on which I want to write unit test:
Response response = request.get();
if (response.getStatusInfo().getFamily().equals(Response.Status.Family.SUCCESSFUL)) {
return response.readEntity(type);
}
I'm able to create the scenario where HTTP request returns a valid response using the below code:
stubFor(get("someUrl").willReturn(aResponse().withStatus(200)));
I want to create another scenario where the method call response.readEntity(type) throws an exception. For this, I require that request.get() method returns me a mocked object so that I can define the desired behavior on the mocked object.
I read the documentation provided at http://wiremock.org/docs to find how to do this behavior but didn't find any way to return a mocked object as HTTP response.
Also, the request variable is not injected and hence I can't mock it directly.
You cannot do something like
stubFor(get("/$metadata?annotations=true").willReturn(aResponse().withStatus(200).withBody(Mock()));. It is because wiremock acts only as http server mock. Only thing you can configure is response (ex. in JSON).
What you can do is to return for example 400 and error code body from wiremock and check if you code accepts this message and act on it correctly.
I am writing some contract tests and I am trying to mock my controller in order to test the wanted method. My method should only return status code 200, so not an object, and I do not know how to write this with Mono or Flux and I get an error because of that.
I tried something like this, but it does not work:
Mono<Integer> response = Mono.just(Response.SC_OK);
when(orchestration.paymentReceived(purchase)).thenReturn(response);
How should I write my "when" part in order to verify it returns status code 200?
In order to check response status code you will need to write a more complicated test, using WebTestClient. Like so:
Service service = Mockito.mock(Service.class);
WebTestClient client = WebTestClient.bindToController(new TestController(service)).build();
Now you are able to test:
serialization to JSON or other types
content type
response code
path to your method
invoked method (POST,GET,DELETE, etc)
Unit tests do not cover above topics.
// init mocks
when(service.getPersons(anyInt())).thenReturn(Mono.just(person));
// execute rest resource
client.get() // invoked method
.uri("/persons/1") // requested path
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk() // response code
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody()
.jsonPath("$.firstName").isEqualTo(person.getFirstName())
.jsonPath("$.lastName").isEqualTo(person.getLastName())
// verify you have called your expected methods
verify(service).getPerson(1);
You can find more examples here. Above test is also does not require Spring context, can work with mock services.
I have a Spring boot project with a service, which basically calls a private method, which does:
webClient.post().uri().accept(...).body(...).exchange()
and then puts a subscribe(...) on it, which simply logs the result.
Everything works fine, but now I need to test this, and this is where things start to get interesting.
By far, I've tried MockServer, okhttp, Spring's WebMockServer(or something), and only MockServer was willing to work at some point properly, while okhttp latest wants junit.rules.* (which is problematic to
achieve), WebMockServer specifically wants RestTemplate.
Google does give out examples, in which a webClient logic method is left without a .exchange() call, giving a chance to call .block() in the test, but I'm not willing to expose a private method just in order to workaround the async calls.
Currently I'm struggling with DEEP_STUB strategy of Mockito to mock out the actual webClient chain, but this fails to work from the box, and I'm trying to make it work while writing this question.
So the question is - is there a proper way to test a webClient with an async call (maybe a MockServer with a timeout to the verification or something)?
Wiremock seem to be an accepted way of mocking external http servers and it is now part of spring test framework.
Here is an introduction: https://www.baeldung.com/introduction-to-wiremock
Otherwise WebTestClient is a bean which might come in handy at this point if you can inject it in the constructor of your service class?
#SpringBootTest(webEnvironment = RANDOM_PORT)
#AutoConfigureWebClient
class DemoResourceTest {
#Autowired
private WebTestClient webTestClient;
#BeforeEach
public void setUp() {
webTestClient = webTestClient
.mutate()
.responseTimeout(Duration.ofMillis(1000))
.build();
}
#Test // just for reference
void some_test_that_gives_success() {
webTestClient.get()
.uri(uriBuilder -> uriBuilder
.path("/world")
.queryParam("requestString", "hello")
.build())
.accept(APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody(String.class);
}
}
I have a method that makes a call to external endpoint using io.vertx.ext.web.client.WebClient . I am not able to test the handler method of it.
This is the method that needs to be tested:
public void freshdeskPostRequest(CompletableFuture<ResponseObject> completableFuture, String url, JsonObject jsonObject, String action) {
webClient.postAbs(url)
.putHeader("Content-type", "application/json")
.putHeader(Constants.AUTHORIZATION, freshdeskAuthHandler)
.timeout(fresdeskTimeout)
.sendJsonObject(jsonObject, httpResponseAsyncResult -> {
getFreshdeskResponse(completableFuture, action, httpResponseAsyncResult);
});
}
The method in it getFreshdeskResponse needs to be tested by making a mock call to the url. But the method is called in handler so I am not sure how to mock the call and execute the handler. I checked several answers on the forum as well went through the docs but none of them helped. Please help. I am using Junit, Mockito as testing frameworks. Please help.
You could use ArgumentCaptor of the mockito library to capture the lambda expression and trigger the lambda manually. Ex :
#Captor
ArgumentCaptor<SomeHandlerType> captor; // create ArgumentCaptor for handler
SomeHandlerType is the type of handler. Then call the sendJsonObject() with captor.capture() like so -
mockedRequest.sendJsonObject(captor.capture()); // capture the argument
SomeHandlerType handler = captor.getValue(); // get the handler lambda
handler.handle(dummyResponse); // trigger the handler manually