【问题标题】:TestRestTemplate throws exception for 4xx status codesTestRestTemplate 为 4xx 状态码抛出异常
【发布时间】:2019-02-14 17:08:18
【问题描述】:

我正在为 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);

我知道我可以通过以下方式解决这个问题:

  • 使用字符串和 Gson 反序列化,或
  • 使用 RestTemplate 代替 TestRestTemplate 并处理 HttpStatusCodeException,或者
  • 在状态码不是 2xx 时不尝试反序列化的覆盖方法

但由于 TestRestTemplate 应该是一个容错便利子类,我本来希望它开箱即用,而不是尝试反序列化错误响应。

我在这里遗漏了什么吗?我用错了吗?

【问题讨论】:

  • >2 年后,这对我来说也是一个问题。如果 HttpResponse 不是 200,您会期望 TestRestTemplate 不会尝试将错误反序列化到复杂对象中...

标签: java spring spring-boot resttemplate spring-boot-test


【解决方案1】:

很抱歉重新提出这个近 2 年的问题,但我在使用 Spring TestRestTemplate 和否定验证测试时遇到了一个非常相似的问题。

正如 Martin 在他的回答中提到的,TestRestTemplate 不包括通常与正确的RestTemplate 相关联的ResponseErrorHandler。但响应的正文仍将包含一条错误消息,而不是User 的列表。

在我的例子中,我的 web 应用程序有 @ControllerAdvice,它包含了所有常见的验证错误(MethodArgumentNotValidExceptionMethodArgumentTypeMismatchException 等)并返回了我自己的类 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"))

在您的情况下,我确信您返回的错误消息是错误消息类的一个实例。您可能通过返回字符串并手动编组的建议意识到了这一点。如果您知道您的错误消息代表什么类,您可以简单地将其替换为 ResponseEntity 中的类型。

【讨论】:

    【解决方案2】:

    我希望实现 ResponseErrorHandler 可以帮助您解决这个问题。

    但是对于 RestTemplate 来说,在不成功的结果上抛出错误是默认行为,你确定你还没有覆盖它吗?也许您可以使用专用的RestTemplate 进行测试。

    来源:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/HttpClientErrorException.html

    收到 HTTP 4xx 时抛出异常。

    要实现ResponseErrorHandler,请参阅https://www.baeldung.com/spring-rest-template-error-handling

    编辑:确实,对于 TestRestTemplate,这不是默认行为,它适用于具有以下好处的集成测试:

    该模板以一种对测试友好的方式运行,不会在服务器端错误上抛出异常
    ...

    • 不遵循重定向(因此您可以断言响应位置)。
    • Cookie 被忽略(因此模板是无状态的)。

    在您的情况下,您在测试代码中承诺返回用户列表,而事实并非如此,我不希望代码对此具有弹性,我什至会说,对于这种情况,RestTemplate 可能更有意义。

    【讨论】:

    • 使用 RestTemplate - 是的。异常是错误的默认和预期行为。但是,我正在尝试使用 TestRestTemplate,其中预期的行为,至少在我的理解中,应该处理您的测试的异常,但没有彻底定义。具体来说:如果您运行一个解析对 String 的响应的查询,TestRestTemplate 会正确包装它并为您处理异常。如果将预期响应从 String 更改为复杂类,解析响应时会出错。您可以查看我在原帖中提供的示例以供参考。
    • 好的,那我相信你用错了。因为 TestRestTemplate 确实并不是说您需要一个用户列表,而在发生错误时不返回任何内容,这只是您的测试中的无效状态。如果您需要验证 200 响应的行为,您最好使用普通的 RestTemplate 和一些 ExpectedException Rule
    • 我现在才知道你的问题已经六个月了,你最后是怎么解决的:-)
    • 我必须恭敬地不同意。据我了解,专门用于测试(集成或其他)的实现有望正确处理错误和故障 - 因为这是这些测试预期要捕获的。 TestRestTemplate 已经以方便的方式返回了相关的错误状态码,我对现有实现的问题是我不明白为什么在状态不成功时它仍然尝试将响应转换为预期的结果类型。
    • 最后,由于时间限制,我的解决方案是返回String值,断言状态码判断是否成功,成功后将字符串转换为期望的类型,以便进一步断言.我没有太多的测试,所以在性能方面这是一个不错的折衷方案。对于未来类似的情况,我可能会研究替代的 REST 客户端。
    猜你喜欢
    • 2021-01-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-23
    • 2017-06-25
    • 2020-02-15
    • 2018-12-30
    相关资源
    最近更新 更多