Cannot test WebClient exception situation - spring

I use Spring WebClient and write a test by following this page.
Everything is ok except from the exception test. I tried to use many different exception classes, but each time I get:
java.lang.AssertionError: Expecting a throwable with cause being an instance >of:java.lang.RuntimeException but current throwable has no cause.
The test method is here:
#Test
void throwsProductServiceExceptionWhenErrorStatus() throws JsonProcessingException {
server.enqueue(
new MockResponse()
.setResponseCode(500)
.setHeader("content-type", "application/json")
.setBody("{}"));
assertThatThrownBy(() -> client.findByName("flower"))
.hasCauseInstanceOf(RuntimeException.class);
// I also tried many other exception types e.g. JsonProcessingException
}
Here is my WebClient method that I want to create exception test:
private String fetchData(String title, String uri) {
// make GET call with WebClient.get and use a Mono of String to get the response as JSON string
return webClient
.get()
.uri(uri, title)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatus::is5xxServerError,
error -> Mono.error(new RuntimeException(ERROR_WHEN_FETCHING_DATA)))
.bodyToMono(String.class)
.block();
}
Any idea?

Related

Exception Handling for async WebClient request

Recently I have been working with WebClient. I am creating both a sync request and an async request. In a sync request, I am able to catch WebClientResponseException whereas when I try to catch WebClientResponseException (tried with generic Exception as well, didn't work) in an async request, it cannot be caught. I couldn't find any example on the internet. I am not sure if such exception handling is possible in async WebClient requests.
Here are the code examples:
Sync Request (Where the exception can be caught)
{
webClient
.post()
.uri(POST_ENDPOINT)
.bodyValue(cloudEvent)
.header("Authorization", token)
.retrieve()
.toEntity(String.class)
.block();
 
}
catch(final WebClientResponseException e)
{
log.error(String.valueOf(e));
throw new MyCustomException ("");
}
Async Request (Where the exception cannot be caught)
try
{
stream=
webClient
.get()
.uri(GET_ENDPOINT)
.header("Authorization", token)
.retrieve()
.bodyToFlux(Any_Parametrized_Type_Reference);
}
catch(ffinal WebClientResponseException e)
{
log.error(String.valueOf(e));
throw new MyCustomException ("");
}
Thanks in advance for any response.
Async means that the process will be transferd to another thread. bodyToFlux will do this and finish the execution, but you don´t have any control over that thread because, in your current thread, the function was executed and it doesn't trigger an issue.
This is similar to Promises in JS, where you should have a callback in case of any exception.
after bodyToFlux, you can add those callbacks.
webClient.get()
.uri("https://baeldung.com/path")
.retrieve()
.bodyToFlux(JsonNode.class)
.timeout(Duration.ofSeconds(5))
.onErrorMap(ReadTimeoutException.class, ex -> new HttpTimeoutException("ReadTimeout"))
.onErrorReturn(SslHandshakeTimeoutException.class, new TextNode("SslHandshakeTimeout"))
.doOnError(WriteTimeoutException.class, ex -> log.error("WriteTimeout"))
onErrorMap, onErrorReturn and doOnError receive your callbacks and will execute the lambda methods in case of exception.
More info here: https://www.baeldung.com/spring-webflux-timeout#exception-handling
Thanks to #CarlosCárdenas; the following changes worked for me when I locally tested it.
private static final Function<WebClientRequestException, MyCustomException>
EXCEPTION_MAPPER =
ex ->
new MyCustomException(ex.getCause());
webClient
.get()
.uri(GET_ENDPOINT)
.header("Authorization", token)
.retrieve()
.bodyToFlux(Any_Parametrized_Type_Reference)
.onErrorMap(WebClientRequestException.class, EXCEPTION_MAPPER)
.doOnError(MyCustomException.class, ex -> myExceptionHandlingMethod(ex));
Currently, I have an issue with the related unit test. It doesn't throw an exception when it should, so I cannot catch the exception. I think I'm missing something about testing asynchronous flow. My test looks like this:
#Test
void myTest()
{
// Given
…
//When
final Executable executeMyMethod=
() -> myMethod(parameter1, parameter2);
// Then
// await().pollDelay(Duration.FIVE_SECONDS).untilAsserted(() -> assertTrue(true)); -> I’m not sure if it is needed or not
assertThrows(MyCustomException.class, executeMyMethod);
}

Spring boot - WebFlux - WebTestClient - convert response to responseEntity

I have a Reactive controller which returns:
#ResponseBody
#GetMapping("/data")
public Mono<ResponseEntity<Data>> getData() {
//service call which returns Mono<Data> dataMono
Mono<ResponseEntity<Data>> responseEntityMono = dataMono
.map(data -> ResponseEntity.ok(data))
.doFinally(signalType -> {});
return responseEntityMono;
}
I am using WebTestClient to test this endpoint, but I want to extract the response entity for cucumber to validate further.
I tried this:
#Autowired private WebTestClient webTestClient;
public ResponseEntity get() {
EntityExchangeResult < ResponseEntity > response = webTestClient.get()
.uri(uriBuilder ->
uriBuilder
.path(VISUALIZATION_URL)
.build())
.header("Accepts", "application/json")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON_VALUE)
.expectBody(ResponseEntity.class)
.returnResult();
return response.getResponseBody();
}
but I am getting an error. I can get the JSON by doing:
public String get() {
BodyContentSpec bodyContentSpec = webTestClient.get()
.uri(uriBuilder ->
uriBuilder
.path(URL)
.build())
.header("Accepts", "application/json")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON_VALUE)
.expectBody();
return new String(bodyContentSpec.returnResult().getResponseBody());
}
But I am trying to see if I can get the whole ResponseEntity so that I can validate headers, caching headers, and body.
You will never be able to get a ResponseEntity in your test. WebTestClient.ResponseSpec returned from exchange() is the only way to check the answer from your Controller. ResponseEntity is just the object you return in your method but once Jackson serializes it, it is converted to a regular HTTP response (in your case with JSON in his body and regular HTTP headers).

How to handle HTTP status code in Spring Webclient

I'm stuck trying to do simple error handling when calling a remote service. The service returns a Map. The behaviour I'm looking for is:
HTTP 200 --> Return body (Map<String, String>).
HTTP 500 --> Throw a particular exception
HTTP 404 --> Simply return Null.
Here's my code:
private Map<String, String> loadTranslations(String languageTag) {
try {
WebClient webClient = WebClient.create(serviceUrl);
Map<String, String> result = webClient.get()
.uri("/translations/{language}", languageTag)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(httpStatus -> HttpStatus.NOT_FOUND.equals(httpStatus),
clientResponse -> Mono.error(new MyServiceException(HttpStatus.NOT_FOUND)))
.onStatus(HttpStatus::is5xxServerError, response -> Mono.error(new MyServiceException(response.statusCode())))
.bodyToMono(Map.class)
.block();
return result;
} catch (MyServiceException ex) { // doesn't work as in reality it throws ReactiveException
....
}
}
I don't know how to have the result of block() return NULL (or something that I can interpret as "404 was received"). The idea would be to just return NULL on 404 and throw an exception on 500.
I tried returning Mono.empty() but in that case the result variable contains the body of the response as Dictionary (I'm using standard Spring error bodies that contain timestamp, path, message).
What I'm doing wrong?
Thank you,

spring mockMVC testing method GET

i created post method in mockMVC (in spring boot project)
This is my method testing
This is my method testing
#Test
public void createAccount() throws Exception {
AccountDTO accountDTO = new AccountDTO("SAVINGS", "SAVINGS");
when(addaccountService.findByName("SAVING")).thenReturn(Optional.empty());
when(addaccountService.createAccount(any())).thenReturn(createdAccountDTO);
CreatedAccountDTO createdAccountDTO = new CreatedAccountDTO("a#wp.pl", "SAVINGS", "1234rds", uuid);
mockMvc.perform(
post("/account").contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(AccountNewDTO)))
.andExpect(status().isCreated())
.andExpect(header().string("location", containsString("/account/"+uuid.toString())));
System.out.println("aaa");
}
I want to write GET method.
how to write a get method in mock mvc? how to verify whether what I threw was returned?
You can try the below for Mockmvc perform get and post methods
For get method
#Autowired
private MuffinRepository muffinRepository;
#Test
public void testgetMethod throws Exception(){
Muffin muffin = new Muffin("Butterscotch");
muffin.setId(1L);
BddMockito.given(muffinRepository.findOne(1L)).
willReturn(muffin);
mockMvc.perform(MockMvcRequestBuilders.
get("/muffins/1")).
andExpect(MockMvcResutMatchers.status().isOk()).
andExpect(MockMvcResutMatchers.content().string("{\"id\":1, "flavor":"Butterscotch"}"));
}
//Test to do post operation
#Test
public void testgetMethod throws Exception(){
Muffin muffin = new Muffin("Butterscotch");
muffin.setId(1L);
BddMockito.given(muffinRepository.findOne(1L)).
willReturn(muffin);
mockMvc.perform(MockMvcRequestBuilders.
post("/muffins")
.content(convertObjectToJsonString(muffin))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResutMatchers.status().isCreated())
.andExpect(MockMvcResutMatchers.content().json(convertObjectToJsonString(muffin)));
}
If the response is empty then make sure to override equals() and hashCode() method on the Entity your repository is working with
//Converts Object to Json String
private String convertObjectToJsonString(Muffin muffin) throws JsonProcessingException{
ObjectWriter writer = new ObjectWriter().writer().withDefaultPrettyPrinter();
return writer.writeValueAsString(muffin);
}
You can use the static get method of the class MockMvcRequestBuilders, see: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.html#get-java.lang.String-java.lang.Object...-
Example:
mockMvc.perform(get("/account")).andExpect(...);
If you throw an exception within your controller method it will typically trigger execution of an exception handler which transforms the exception into a HTTP error response. By default, you could check if the status of the response was 500. If you have implemented your own exception handler you may want to check the response body as well to verify if it contains the expected error data.

Spring mvc - Configuring Error handling for XML and JSON Response

i have one REST API method :which will return Xml as response . Just for simplicity assume it throws simple Exception.
#RequestMapping(value = "machine/xmlData", method = RequestMethod.GET, produces = "application/xml")
public ResponseEntity<String> getXml(HttpServletRequest request)
throws Exception {
return getDataFromService();
}
Now i am handling the Exception in REST Controller like this.
This is generic Exception Handle method, for other API methods as well.(Xml or JSON Response)
#ExceptionHandler(Exception.class)
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public ResponseEntity HandleException(Exception ex, HttpServletRequest request) {
ex.printStackTrace();
// here logic to generate Custom error Object
return new ResponseEntity<Object>(customErrorObject, HttpStatus.INTERNAL_SERVER_ERROR);
}
Case 1: Accept :"application/xml" and valid Response from Service
Everything works fine.
Case 2: Accept :"application/xml" and Exception from Service
then i get 406 Not Representable
As per my understanding it is
because ResponseEntity from HandleException is JSON and accept header
is "application/xml" thats why i am getting 406.
Is there anyway that i can send the error Response from HandleException method as xml and json ?
I know on REST API methods we can define something like this produces={"application/json","application/xml"} i am struggling to put this on HandleException Method.
Any tip would be of great help.
Thanks.
You could take advantage of the spring-mvc HttpMessageConverters by using the #ResponseBody annotation( https://spring.io/blog/2013/05/11/content-negotiation-using-spring-mvc). This annotation is responsible for choosing the correct messageConverter for a given response type.
For your response to be xml or json compatible you need to do the following:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class WrappedExceptionResponse {
public String respone;
public String getRespone() {
return respone;
}
public void setRespone(String respone) {
this.respone = respone;
}
}
And change your exception handler method to
#ExceptionHandler(Exception.class)
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public #ResponseBody WrappedExceptionResponse HandleException(Exception ex, HttpServletRequest request) {
// ex.printStackTrace();
// here logic to generate Custom error Object
WrappedExceptionResponse resp=new WrappedExceptionResponse();
resp.setRespone(ex.getMessage());
return resp;
And then your exception response would be dependent on the content-type you give.

Resources