简体   繁体   中英

TestRestTemplate throws exception for 4xx status codes

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?

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.

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 .

Maybe i did not understand the problem but why you did not catch the exception by using RestClientException class. If this is not the case then you need to try the work arounds you mentioned.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM