使用 TestRestTemplate 时出现异常

Posted

技术标签:

【中文标题】使用 TestRestTemplate 时出现异常【英文标题】:Exception when Using TestRestTemplate 【发布时间】:2015-02-05 03:23:08 【问题描述】:

我有普通的 SpringBoot 应用程序,带有自定义身份验证过滤器,可以正常工作。

但是在集成测试中使用 TestRestTemplate 时遇到问题。

我想在这里检查信用卡错误的用户是否无法登录。 但不是 ResponseEntity 与 401 状态我得到了异常:

org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: cannot retry due to server authentication, in streaming mode; nested exception is java.net.HttpRetryException: cannot retry due to server authentication, in streaming mode
    at org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.readJavaType(MappingJackson2HttpMessageConverter.java:228)
    at org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.read(MappingJackson2HttpMessageConverter.java:220)
    at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:95)
    at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:795)
    at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:779)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:559)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:512)
    at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:454)
    at cz.angular.security.basic.rest.AuthorizeControllerTest.userWithWrongCreditials(AuthorizeControllerTest.java:64)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:233)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:87)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:176)
    at org.junit.runners.Suite.runChild(Suite.java:127)
    at org.junit.runners.Suite.runChild(Suite.java:26)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:74)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:211)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:67)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: java.net.HttpRetryException: cannot retry due to server authentication, in streaming mode
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1280)
    at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:379)
    at org.springframework.http.client.SimpleClientHttpResponse.getRawStatusCode(SimpleClientHttpResponse.java:48)
    at org.springframework.http.client.AbstractClientHttpResponse.getStatusCode(AbstractClientHttpResponse.java:33)
    at org.springframework.web.client.DefaultResponseErrorHandler.getHttpStatusCode(DefaultResponseErrorHandler.java:56)
    at org.springframework.web.client.DefaultResponseErrorHandler.hasError(DefaultResponseErrorHandler.java:50)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:552)
    ... 42 more

内部弹簧抛 org.springframework.security.authentication.BadCredentialsException 但在实际应用程序使用中它被转换为 json 响应。

当使用 curl 时,我得到了正常的响应。

curl -H "Content-Type: application/json" -d '"name":"user","password":"password-wrong"' http://localhost:8080/login
"timestamp":"2014-12-07T10:07:27.166+0000","status":401,"error":"Unauthorized","exception":"org.springframework.security.authentication.BadCredentialsException","message":"Bad credentials","path":"/login"

测试代码在这里:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
@IntegrationTest("server.port:9999")
public class AuthorizeControllerTest 

  public static final String LOCALHOST = "http://localhost:9999";

...

  @Test
  public void userWithWrongCreditials() throws Exception 
    RestTemplate rest = new TestRestTemplate();

    Credentials credentials = new Credentials();
    credentials.setName("user");
    credentials.setPassword("password-wrong");

    ResponseEntity<Map> response =
      rest.exchange(
        LOCALHOST + "/login",
        HttpMethod.POST,
        new HttpEntity<Credentials>(credentials),
        Map.class);

    assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
  

整个项目可以在存储库中看到

https://bitbucket.org/winsik/security-token-***/

您可以在 AuthorizeControllerTest 中看到通过的测试和失败的测试。

我尝试了很多东西,比如设置内容类型等等,但都没有成功。 我会很感激任何想法。

【问题讨论】:

为我工作。你确定那是 Spring Boot 中的 TestRestTemplate 吗? 是的,它来自 Spring Boot 的 TestRestTemplate。我会尽快提供完整的代码,也许过滤器实现有问题,但我很好奇为什么应用程序按预期工作(输入错误密码时返回 401) @DaveSyer 我已经编辑了问题并添加了包含整个代码的存储库。 bitbucket.org/winsik/security-token-*** 在 AuthorizeControllerTest 中您可以看到通过的测试和失败的测试。 【参考方案1】:

我认为 Jackson 不会知道如何将 JSON 转换为 Object。试试Map 什么的?

【讨论】:

在成功登录测试中,我使用了 UserInfo 对象,这当然可以。在错误的登录测试中,我也尝试过 Map/LinkedHashMap/String,Object 是我最后一次尝试,但结果始终相同。 也许您应该更新问题以更接近可能有效的内容(即使用 Map 作为响应类型)?【参考方案2】:

您需要 Apache HTTP 客户端才能使用 TestRestTemplate 自己处理错误。这对我的项目有用

    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <scope>test</scope>
    </dependency>

(我还删除了重复的 junit、mockito 和 spring-test 依赖项。)

【讨论】:

是的,它有效。非常感谢,你帮了我很多。因此,如果我得到正确的 httpclient 将取代 TestRestTemplate 中的默认 httpClient? 好的,你节省了我的时间。非常感谢。但是,为什么这个技巧能解决问题呢?! @Dave 在 Spring 5 中仍然如此吗?在 RestTemplate:741 ResourceAccessException 中,当IOException 被捕获时似乎总是抛出 我不认为有什么根本改变。注:这个问题是关于TestRestTemplate 我所要做的就是放弃这个依赖,突然这个异常就消失了。【参考方案3】:

这解决了我的问题。

参考:https://github.com/spring-projects/spring-framework/issues/21321

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());   

      restTemplate.setErrorHandler(new DefaultResponseErrorHandler() 
       public boolean hasError(ClientHttpResponse response) throws IOException
           HttpStatus statusCode = response.getStatusCode(); 
           return statusCode.series() == HttpStatus.Series.SERVER_ERROR; 
       
     );

【讨论】:

以上是关于使用 TestRestTemplate 时出现异常的主要内容,如果未能解决你的问题,请参考以下文章

使用 FolderBrowserDialog 时出现异常

使用 strncpy 时出现异常

为啥使用 readUTF 阅读时出现 EOF 异常?

iOS:更新数据时出现CoreData异常

尝试使用 DataflowRunner 时出现 ClassNotFound 异常

使用 aCoder 编码时出现 Swift PFFile 异常