Я пишу тесты компонентов для приложения Spring-Boot, чтобы проверить мою конфигурацию безопасности. Поэтому я запускаю тесты, которые должны проверять как успешные ответы, так и статус «запрещено». Проблема, с которой я сталкиваюсь, заключается в том, что, поскольку мой вызов REST ожидает сложный JSON, для заблокированных вызовов тесты завершаются неудачно, потому что TestRestTemplate пытается десериализовать тело ответа, которого нет.
Я запускаю приложение Spring-Boot, и класс тестов аннотируется:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
Я пытаюсь протестировать REST API, который должен возвращать список пользователей. Упрощенная версия вызова будет такой:
ResponseEntity<List<User>> responseEntity = testRestTemplate.exchange(URL, HttpMethod.GET, entity, new ParameterizedTypeReference<List<User>>() {});
где TestRestTemplate автоматически подключается Spring, а объект содержит информацию об авторизации.
Для несанкционированного запроса я получаю сообщение об ошибке, например:
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]
Если я изменю объект ответа на получение строки вместо списка, я получу ответ и смогу проверить статус и увидеть, что он «запрещен».
ResponseEntity<String> responseEntity = testRestTemplate.exchange(URL, HttpMethod.GET, null, String.class);
Я знаю, что могу обойти это следующим образом:
но поскольку TestRestTemplate должен быть отказоустойчивым удобным подклассом, я ожидал, что он не будет пытаться десериализоваться при ответе на ошибку.
Я что-то упустил здесь? Я использую это неправильно?




Я ожидаю, что реализация ResponseErrorHandler может помочь вам обойти это.
Но для RestTemplate по умолчанию выдаются ошибки при неуспешных результатах. Вы уверены, что еще не переопределили это? Возможно, вы могли бы использовать специальный RestTemplate для своего теста.
Exception thrown when an HTTP 4xx is received.
Для реализации ResponseErrorHandler см. https://www.baeldung.com/spring-rest-template-error-handling
РЕДАКТИРОВАТЬ: Действительно, для TestRestTemplate это не поведение по умолчанию, оно предназначено для интеграционных тестов со следующими преимуществами:
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).
В вашем случае вы обещаете в своем тестовом коде, что будет возвращен список пользователей, хотя это не так, я бы не ожидал, что код будет устойчивым к этому, я бы даже сказал, что в этом случае RestTemplate может иметь больше смысла.
Использование RestTemplate — да. Исключения — это поведение по умолчанию и ожидаемое поведение для ошибок. Однако я пытаюсь использовать TestRestTemplate, где ожидаемое поведение, по крайней мере, насколько я понимаю, должно обрабатывать исключения для ваших тестов, но не определено полностью. В частности: если вы запускаете запрос, который анализирует ответ на String, TestRestTemplate правильно его оборачивает и обрабатывает исключение за вас. Если вы измените ожидаемый ответ со String на сложный класс, я получаю сообщение об ошибке при анализе ответа. Вы можете увидеть примеры, которые я предоставил в исходном посте для справки.
Хорошо, тогда я верю, что вы используете его неправильно. Поскольку TestRestTemplate действительно не предназначен для того, чтобы сказать, что вам нужен список пользователей, хотя в случае ошибки ничего не возвращается, это просто недопустимое состояние в вашем тесте. Если вам нужно поведение для проверки ответа 200 или нет, вам лучше использовать обычный RestTemplate и немного ExpectedExceptionRule.
Я только сейчас понимаю, что вашему вопросу шесть месяцев, как вы его решили в конце концов :-)
Я должен с уважением не согласиться. Насколько я понимаю, реализация, специализированная для тестирования (интеграция или что-то другое), должна правильно обрабатывать ошибки и сбои, поскольку ожидается, что эти тесты отловят именно это. TestRestTemplate уже возвращает соответствующий код статуса ошибки в удобном виде, моя проблема с существующей реализацией в том, что я не понимаю, почему она все еще пытается преобразовать ответ в ожидаемый тип результата, когда статус не успешен.
В конце концов, из-за ограничений по времени, мое решение состояло в том, чтобы возвращать значения String, утверждать код состояния, чтобы убедиться, успешно это или нет, и преобразовывать строку в ожидаемый тип в случае успеха для дальнейших утверждений. У меня было не так много тестов, поэтому с точки зрения производительности это был достойный компромисс. Для подобных ситуаций в будущем я, вероятно, рассмотрю альтернативные клиенты REST.
Извиняюсь за воскрешение этого вопроса почти двухлетней давности, но я столкнулся с очень похожей проблемой при работе с Spring TestRestTemplate и отрицательными проверочными тестами.
Как упомянул Мартин в своем ответе, TestRestTemplate не включает ResponseErrorHandler, который обычно ассоциируется с правильным RestTemplate. Но тело ответа все равно будет содержать сообщение об ошибке вместо списка User.
В моем случае в моем веб-приложении было @ControllerAdvice, которое оборачивало все распространенные ошибки проверки (MethodArgumentNotValidException, MethodArgumentTypeMismatchException и т. д.) и возвращало экземпляр моего собственного класса ErrorMessageDto. Контроллер маршалирует это в JSON вместо ожидаемого ответа.
Мой компонентный тест изначально пытался поймать HttpStatusCodeException, потому что это вызывается обычным RestTemplate. В тесте исключение не выбрасывалось (из-за отсутствия ResponseErrorHandler) и мой restTemplate.postForObject(path, request, MyResponse.class) просто возвращал пустую версию MyResponse.
Прочитав описание Мартина и перейдя по ссылкам, я изменил его на
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"))
В вашем случае я уверен, что сообщение об ошибке, которое вы возвращаете, является экземпляром класса сообщений об ошибках. Вы, вероятно, поняли это, когда предложили возвращать String и сортировать вручную. Если вы знаете, какой класс представляет ваше сообщение об ошибке, вы можете просто заменить его в качестве типа в вашем ResponseEntity.
>2 года спустя и для меня это тоже проблема. Вы ожидаете, что
TestRestTemplateне будет пытаться десериализовать ошибку в сложный объект, если этот HttpResponse не равен 200...