TestRestTemplate выдает исключение для кодов состояния 4xx

Я пишу тесты компонентов для приложения 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);

Я знаю, что могу обойти это следующим образом:

  • Использование String и десериализация с помощью Gson или
  • Использование RestTemplate вместо TestRestTemplate и обработка исключения HttpStatusCodeException или
  • Методы переопределения, чтобы не пытаться десериализовать, когда код состояния не 2xx

но поскольку TestRestTemplate должен быть отказоустойчивым удобным подклассом, я ожидал, что он не будет пытаться десериализоваться при ответе на ошибку.

Я что-то упустил здесь? Я использую это неправильно?

>2 года спустя и для меня это тоже проблема. Вы ожидаете, что TestRestTemplate не будет пытаться десериализовать ошибку в сложный объект, если этот HttpResponse не равен 200...

orpheus 09.06.2021 00:20
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
1
2 186
2

Ответы 2

Я ожидаю, что реализация ResponseErrorHandler может помочь вам обойти это.

Но для RestTemplate по умолчанию выдаются ошибки при неуспешных результатах. Вы уверены, что еще не переопределили это? Возможно, вы могли бы использовать специальный RestTemplate для своего теста.

Источник: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/HttpClientErrorException.html

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 на сложный класс, я получаю сообщение об ошибке при анализе ответа. Вы можете увидеть примеры, которые я предоставил в исходном посте для справки.

maor 01.09.2019 12:27

Хорошо, тогда я верю, что вы используете его неправильно. Поскольку TestRestTemplate действительно не предназначен для того, чтобы сказать, что вам нужен список пользователей, хотя в случае ошибки ничего не возвращается, это просто недопустимое состояние в вашем тесте. Если вам нужно поведение для проверки ответа 200 или нет, вам лучше использовать обычный RestTemplate и немного ExpectedExceptionRule.

Martin van Wingerden 01.09.2019 20:59

Я только сейчас понимаю, что вашему вопросу шесть месяцев, как вы его решили в конце концов :-)

Martin van Wingerden 01.09.2019 21:08

Я должен с уважением не согласиться. Насколько я понимаю, реализация, специализированная для тестирования (интеграция или что-то другое), должна правильно обрабатывать ошибки и сбои, поскольку ожидается, что эти тесты отловят именно это. TestRestTemplate уже возвращает соответствующий код статуса ошибки в удобном виде, моя проблема с существующей реализацией в том, что я не понимаю, почему она все еще пытается преобразовать ответ в ожидаемый тип результата, когда статус не успешен.

maor 03.09.2019 09:59

В конце концов, из-за ограничений по времени, мое решение состояло в том, чтобы возвращать значения String, утверждать код состояния, чтобы убедиться, успешно это или нет, и преобразовывать строку в ожидаемый тип в случае успеха для дальнейших утверждений. У меня было не так много тестов, поэтому с точки зрения производительности это был достойный компромисс. Для подобных ситуаций в будущем я, вероятно, рассмотрю альтернативные клиенты REST.

maor 03.09.2019 10:05

Извиняюсь за воскрешение этого вопроса почти двухлетней давности, но я столкнулся с очень похожей проблемой при работе с 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.

Другие вопросы по теме

Похожие вопросы