TestRestTemplate throws exception for 4xx status codes - java

I am writing component tests for a Spring-Boot application, to test my security configuration. I am therefore running tests that should test for both successful responses as well as "forbidden" status.
The problem I'm encountering is that because my REST call expects a complex JSON, for blocked calls the tests fail because TestRestTemplate is trying to deserialize a response body that is not there.
I am running a Spring-Boot application, and the tests class is annotated with:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
I am trying to test a REST API that should return a list of users.
A simplified version of the call would be:
ResponseEntity<List<User>> responseEntity = testRestTemplate.exchange(URL, HttpMethod.GET, entity, new ParameterizedTypeReference<List<User>>() {});
where the TestRestTemplate is autowired by Spring, and the entity contains the authorization information.
For unauthorized request, I am getting an error like:
org.springframework.web.client.RestClientException: Error while extracting response for type [java.util.List<my.package.User>] and content type [application/json;charset=UTF-8]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.util.ArrayList` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.ArrayList` out of START_OBJECT token
at [Source: (PushbackInputStream); line: 1, column: 1]
If I change the response entity to receive String instead of List, I receive the response and can check the status and see that it is "forbidden"
ResponseEntity<String> responseEntity = testRestTemplate.exchange(URL, HttpMethod.GET, null, String.class);
I know I can work around this by:
Using String and deserializing with Gson, or
Using RestTemplate instead of TestRestTemplate and handling the HttpStatusCodeException, or
Overriding methods to not try to deserialize when status code is not 2xx
but since TestRestTemplate is supposed to be a fault-tolerant convenience subclass, I would have expected it to out-of-the-box not try to deserialize on error response.
Am I missing something here? Am I using it wrong?

Apologies for resurrecting this almost 2-year-old question, but I ran into a very similar problem while working with Spring TestRestTemplate and negative validation tests.
As Martin mentioned in his answer, TestRestTemplate does not include the ResponseErrorHandler that is normally associated with the proper RestTemplate. But the body of the response will still contain an error message instead of a list of User.
In my case, my web-app had #ControllerAdvice that wrapped all the common validation errors (MethodArgumentNotValidException, MethodArgumentTypeMismatchException, etc) and returned an instance of my own class ErrorMessageDto. The controller will marshal that to JSON instead of the expected response.
My component test initially tried to catch HttpStatusCodeException because that is thrown by the normal RestTemplate. In the test, the exception was not thrown (because of the lack of ResponseErrorHandler) and my restTemplate.postForObject(path, request, MyResponse.class) simply returned an empty version of MyResponse.
After reading Martin's description and following links, I changed it to
ResponseEntity<ErrorMessageDto> responseEntity = restTemplate.postForEntity(path, request, ErrorMessageDto.class);
// Do assertions on the response code and body (using assertj)
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.UNPROCESSABLE_ENTITY);
assertThat(responseEntity.getBody().getErrors())
.extracting("path", "message")
.contains(tuple("firstName", "size must be between 0 and 255"))
In your case, I am sure the error message you are returning is an instance of an error message class. You probably realized this with your suggestion of returning a String and marshalling manually. If you know what class your error message is representing, you can simply replace that as the type in your ResponseEntity.

I expect that implementing a ResponseErrorHandler could help you work-around this.
But for RestTemplate it's the default behavior to throw errors on non-success results, are you sure you did not yet override it? Maybe you could use a dedicated RestTemplate for your test.
Source: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/HttpClientErrorException.html
Exception thrown when an HTTP 4xx is received.
For implementing a ResponseErrorHandler, see https://www.baeldung.com/spring-rest-template-error-handling
EDIT: Indeed for TestRestTemplate this is not default behavior its meant for integration tests with the following benefits:
the template behaves in a test-friendly way by not throwing exceptions on server-side errors
...
Redirects are not followed (so you can assert the response location).
Cookies are ignored (so the template is stateless).
In your case you promise in your test code that a list of users is returned while this is not true, I would not expect the code to be resilient against that, I would even say that for that case a RestTemplate might make more sense.

Related

Springboot: How to override default JSON response structure in case of any type failures?

In my Springboot(2.x.x) application, while using REST Controller, there are two types of failures I need to handle
The custom validation failures, here I can generate any Json Response structure as per my need (i.e. any field I need)
The default validation failures by Springboot (few examples given below and many more). Here it generates either 5xx or 4xx and default few fields in json response. How can override this at application layer and match to my custom failure JSON response?
MissingServletRequestPartException.class, //it will be thrown when one of form param is missing
MissingServletRequestParameterException.class, //it will be thrown when one of request param is missing
MethodArgumentNotValidException.class, //it will be thrown when form json param value is not valid
MethodArgumentTypeMismatchException.class, //it will be thrown when request param type value is mismatched
ConstraintViolationException.class, // it will be thrown when any of request param is not valid
You can validate your request and throw Custom exception for each validation error.
Create Exception handler it catch the exception for you and do the necessary work for you.
You can find the code snippet for spring error handling here

Spring Boot - postForEntity(): what is content type of the response?

I am attempting to set up and run integration tests on a set of Spring Boot microservices that communicate via HTTP REST. I am using the Citrus Framework for the integration test framework.
I have a test scenario that involves one "master" service calling two other services to do work. My test has the calls to start the process and "mock" the worker services. I'll include source below.
I'm running into an issue where I get an exception that seems to indicate that a message the test is expecting to receive (as application/json) is coming through as text/plain and it cannot find a message converter to use. The odd thing is that the message that's is being received should be JSON (or least look like JSON).
I encountered a similar issue on the sending end (the POSTer), where Citrus was having a problem with receiving a message. I traced it down to the fact that I had not been setting any HTTP headers, specifically Accept and Content-Type. Once I set these appropriately, Citrus was happy with what it received.
The service code:
HttpEntity<GenerateRouteCommand> entity =
(HttpEntity<GenerateRouteCommand>) HttpEntityBuilder.createHttpEntity(...);
ResponseEntity<GenerateRouteStatus> response =
rgTemplate.postForEntity(url, entity, GenerateRouteStatus.class);
The exception:
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class edu.mit.ll.mission_services.messages.GenerateRouteStatus] and content type [text/plain;charset=utf-8]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:121)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:994)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:977)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:737)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:670)
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:445)
at edu.mit.ll.mission_services.service.mission_planner.service.MissionPlanner.postGenerateRoute(MissionPlanner.java:214)
at edu.mit.ll.mission_services.service.mission_planner.service.MissionPlanner.planMission(MissionPlanner.java:144)
at edu.mit.ll.mission_services.service.mission_planner.controller.MissionPlannerController$Runner.executeTask(MissionPlannerController.java:51)
at edu.mit.ll.mission_services.common.util.ITask.run(ITask.java:37)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
The entity that's being sent in the POST request has the Accept and Content-Type headers set to application/json. I think I'm OK there. The stack trace above seems to be saying that the response message either has no headers set (or set to the wrong values) and since the test case expects JSON, fails the test.
When these services are run "normally" (i.e. not being driven via Citrus), everything works fine. No problems that I've been able to discern.
I've recently ran into this issue with spring-boot twice while trying to decode REST Respones. I solved this issue by explicitly setting HttpMessageConverters on the RestTemplate.
If I were you I would debug and see what the content of the response actually looks like. If it is JSON try using GsonHttpMessageConverter or if it really is just text, try StringHttpMessageConverter. Alternatively add both to the RestTemplate via the following:
private final RestTemplate restTemplate = new RestTemplateBuilder()
.messageConverters(Lists.newArrayList(new GsonHttpMessageConverter(GsonHelper.getInstance()), new StringHttpMessageConverter()))
.build();
If neither of these converters appear to solve your exception, take a look at all the different implementations of the HttpMessageConverter interface and deduce which one would suite your specific case. There are a fair number of implementations implementing this interface.
Figured out what I needed to do to set the expected content type of application/json for a message returned from a mocked server in Citrus. I was missing the following statement:
.contentType(ContentType.APPLICATION_JSON.getMimeType())
Some context:
// Set route assessor to return response.
runner.http(builder -> builder
.server(raServer)
.send()
.response(HttpStatus.OK)
.messageType(MessageType.JSON)
.contentType(ContentType.APPLICATION_JSON.getMimeType())
.payload(new ClassPathResource("templates/assess-route-status.json")));
Evidently, it defaults to text/plain if not specified.

SpringBoot rest service error handling

There are multiple ways to send custom message in error response of rest api call.
Which is the best way out of these :
1.One way is to use io.swagger.annotations like
#ApiResponses(value = { #ApiResponse(code = 500, message = "error message", response=ErrorDescription.class) })
above method signature.
2.Another way is to define #ControllerAdvice over global exception hanlder.
Which is the better way of two.
Thanks
Your first approach with the swagger annotation won't handle your errors in your application. It's just for documentation purposes so that you can see how your API behaves in an error case. With this annotation, you customize your swagger-ui.html page for your REST endpoint and map the error code to a specific response object and add a custom description for your clients.
To really handle e.g. exceptions with self-defined response types and message you can use #ControllerAdvice and define the result types and messages. In addition, I would also use the Swagger annotation to write some text about the error case and tell the client which response object he can expect.
The following blog post might help you for writing your #ControllerAdvice clas: http://niels.nu/blog/2016/controller-advice-exception-handlers.html

Spring HttpClientErrorException provides no details from response body

I'm updating legacy code that uses the exchange method of Spring 3.1 Framework's RestTemplate class. I came across what appears to be a major omission of detail. When the rest client with which I am attempting to communicate returns a 400 status code, a HttpClientErrorException is thrown but there is no response body to provide detail as to why the server rejected the request. There looks like there is no way to retrieve the response body which would provide very helpful information.
I do not need to figure out what is wrong in my calling code as I have already done that. I just would like to know if there is some way I could access and log the body of the HTTP response if an error occurs on the call.
The response body is actually a property on HttpClientErrorException. It can be accessed via the following two accessors which it inherits from its parent class HttpStatusCodeException:
public byte[] getResponseBodyAsByteArray()
public String getResponseBodyAsString()
Cast your HttpClientErrorException e to HttpStatusCodeException:
((org.springframework.web.client.HttpStatusCodeException) e).getResponseBodyAsString()

rest assured test failing intermittently due to data missing in the response

I have a rest assured test where I am converting the json into the java object and asserting on the java object.In the response object there is a particular value that I am asserting on in the test. When I run the service manually using POSTMAN I always see the parameter value in the response but when my automation tests execute the value is not always present in the response object.
I am suspecting it can be the issue from the service side not sending the consistent response but when hit manually the value is always present.
Test code:
#Test
public void validateResponse() {
RequestObject.name= "Hello";
RequestObject.age="20";
ResponseObject responseObject= given()
.contentType(TestData.CONTENT_TYPE_FOR_TEST)
.body(RequestObject)
.then()
.log()
.everything()
.when()
.post(uri)
.as(ResponseObject.class);
assertNotNull(responseObject.name);
assertNotNull(responseObject.year.age);
I think my test is simple not complicated but the assertion fails intermittently.
Any insight of how to debug more on this?
Your fields in RequestObject looks static and I think it's quite likely the JSON object mapper won't bind the result to static fields. Try make them non-static.

Categories

Resources