I am new to Spring Webflux and I want to know what is the best way to write a unit test to check if an error is being thrown:
public Mono<String> update(UpdateModel updateModel) {
return webClient.post()
.uri(updatePath)
.bodyValue(updateModel)
.retrieve()
.onStatus(httpStatus -> !HttpStatus.OK.equals(httpStatus),
clientResponse -> Mono.error(new ServerSideException("Failed to update")))
.bodyToMono(String.class);
}
What is the best way to write a unit test ?
This is what I am using:
#Test
public void shouldThrowServerErrorIfUpdateServiceCallFails() {
when(responseSpecMock.onStatus(any(), any())).thenReturn(responseSpecMock);
when(responseSpecMock.bodyToMono(String.class)).thenReturn(Mono.error(new ServerSideException("Failed to update")));
var response = client.update(updateModel);
StepVerifier.create(response)
.expectError(ServerSideException.class)
.verify();
}
The error is actually thrown from the lambda, not being thrown from bodyToMono call.
This is obviously not correct, but I don't know any better way to check it via unit test.
In my opinion, the best way to test how WebFlux behaves in both normal and error scenarios is to set up a test web server with Wiremock or Mountebank and avoid mocking completely. This way you are assured that your tests are exercising the WebFlux code in the same way as in real life.
Mocking and stubbing is very useful when used appropriately, but they are usually a waste of time when testing 3rd party libraries such as WebFlux. Shameless plug: I wrote an article to explain this point in more detail.
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.
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 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);
}
}
This is the code for which I am looking to write Junit test cases--
#GET
#Path("/get")
#Produces("application/json")
public User getUser()
{
User user= new com.rest.rahul.User();
user.setEmpid("12");
user.setEmail("DJ#gmail.com");
user.setName("DJ");
return user;
}
What do you want to unit test? I wouldn't bother with unit testing simply setters. But if you want to test the JSON output, you're actually looking for something like an an integration test. A test that bootstraps your application, sets up the webservice and then calls the getUser method.
Take a look at https://blog.codecentric.de/en/2012/05/writing-lightweight-rest-integration-tests-with-the-jersey-test-framework/
Testing a web service with JUnit involves starting up the web service inside the test environment, performing the request, validating the response and then shutting it down.
You can use Jersey's REST Client to perform a request on a server that comes up when the test runs. Get the response and Assert your required conditions on the response object.
More on Jersey's client here:
http://www.mkyong.com/webservices/jax-rs/restful-java-client-with-jersey-client/