控制器测试因MethodArgumentConversionNotSupportedException而失败,但在运行的应用程序中未失败

时间:2018-07-16 10:38:44

标签: rest unit-testing spring-boot spring-boot-test

使用2.0.3 Release of Spring Boot的Spring Boot App。

所以我有这样写的REST API控制器:

@RestController
@RequestMapping("/root/{id}")
@Slf4j
public class RootController {

  @GetMapping
  public ResponseEntity<?> getXXX(
      @PathVariable String id,
      @RequestParam(value = "status") Status status, 
      @RequestParam(value = "comment") String comment,       
@RequestParam(value = "other") Optional<String> other) {
    log.info("Requested getXXX id={} status={} other={} comment={}", id, status, other, comment);

    return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
  }

}

所以有趣的是上面定义中的Optional<String> other。我已经通过调用curl手动测试了上述内容:

curl -v -X GET 'http://localhost:8080/root/ID?status=OK&comment=Comment'

这将导致控制台上的日志记录输出如下:

...Requested getXXX id=ID status=OK other=Optional.empty comment=Comment

并使用像这样的卷曲:

curl -v -X GET 'http://localhost:8080/root/ID?status=OK&comment=Comment&other=MoreOther'

这将导致以下输出:

Requested getXXX id=ID status=OK other=Optional[MoreOther] comment=Comment

到目前为止,一切都很好。

但是,我当然想通过单元测试而不是手动进行检查...所以我写了一个REST控制器测试,看起来像这样:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = RootController.class)
@AutoConfigureMockMvc
public class RootControllerTest {

  @Autowired
  private MockMvc mvc;

  @Test
  public void shouldReturnNotImplemented() throws Exception {
    //@formatter:off
    mvc.perform(
        get("/root/xyz?status={status}&comment={comment}&other={other}", Status.NOTOK, "COMMENT", Optional.<String>of("Other"))
          .characterEncoding("UTF-8")
          .accept(MediaType.ALL)
      )
    .andExpect(
          status().isNotImplemented()
        );
    //@formatter:on
  }

但是不幸的是,以上测试失败了:

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /root/xyz
       Parameters = {status=[OK], comment=[Comment], other=[Optional[Other]]}
          Headers = {Accept=[*/*]}
             Body = null
    Session Attrs = {}

Handler:
             Type = ...RootController
           Method = public org.springframework.http.ResponseEntity<?> .getRoot(java.lang.String,Status,java.lang.String,java.util.Optional<java.lang.String>)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 500
    Error message = null
          Headers = {}
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

有一个例外:org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException是我不理解的东西。

最后一个问题是:为什么测试失败但正在运行的应用程序没有失败?有人对我有提示/想法吗?

更新1:

我还测试了以下内容:

    get("/root/xyz?status={status}&comment={comment}&other={other}", Status.NOTOK, "COMMENT", "Other")

此外,这意味着仅使用字符串。

    get("/root/xyz?status={status}&comment={comment}&other={other}", "NOTOK", "COMMENT", "Other")

正在运行的应用程序可以完美运行,但不幸的是测试无法正常运行。

更新2:

因此在测试中打开调试模式后,我得到以下输出:这使我越来越多地意识到其中存在错误。.因为参数总是转换为String而不是Optional ...并且基于get(..., Object... uriVars) 的参数,看来代码中存在一些问题...

2018-07-16 15:50:21.029 DEBUG 16022 --- [main] s.w.s.m.m.a.RequestMappingHandlerMapping : Looking up handler method for path /root/xyz
2018-07-16 15:50:21.031 DEBUG 16022 --- [main] s.w.s.m.m.a.RequestMappingHandlerMapping : Returning handler method [public org.springframework.http.ResponseEntity<?> de....RootController.getXXX(java.lang.String,de....Status,java.lang.String,java.util.Optional<java.lang.String>)]
2018-07-16 15:50:21.057 DEBUG 16022 --- [main] .w.s.m.m.a.ServletInvocableHandlerMethod : Failed to resolve argument 3 of type 'java.util.Optional'

org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type 'java.util.Optional'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.util.Optional': no matching editors or conversion strategy found
    at org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.resolveArgument(AbstractNamedValueMethodArgumentResolver.java:127)
    at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:124)
    at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:161)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:131)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:877)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:783)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:866)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851)
    at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:71)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
    at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:166)
    at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:133)
    at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:165)
    at de...RootControllerTest.shouldReturnNotImplemented(RootControllerTest.java:35)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73)
    at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)
Caused by: java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.util.Optional': no matching editors or conversion strategy found
    at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:299)
    at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:99)
    at org.springframework.beans.TypeConverterSupport.doConvert(TypeConverterSupport.java:73)
    at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:52)
    at org.springframework.validation.DataBinder.convertIfNecessary(DataBinder.java:692)
    at org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.resolveArgument(AbstractNamedValueMethodArgumentResolver.java:123)
    ... 50 common frames omitted

1 个答案:

答案 0 :(得分:1)

这与您如何加载测试有关。

当您使用@SpringBootTest(classes = RootController.class)指定SpringBootTest一个类时,它将将该类加载到上下文中,即,它允许您指定某些配置等。测试某些集成测试,而不要使用ContextConfiguration

您可以删除RootController并加载完整的测试应用程序上下文,从而有效地加载整个应用程序。

或仅指定

@RunWith(SpringRunner.class)
@WebMvcTest
public class RootControllerTest {

要加载切片测试,这只会加载完整测试WebMVC所需的bean。

工作测试

https://github.com/Flaw101/mockmvctests

修改

我已经更新了示例,并引入了第二个控制器,但仅通过RootControllerRootControllerMock加载到@WebMvcTest(controllers = RootController.class)中。您可以在记录的输出中看到它仅加载该控制器。

2018-07-16 15:34:28.264 INFO 6176 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/root/{id}],methods=[GET]}" onto public org.springframework.http.ResponseEntity<?> com.darrenforsythe.mockmvc.RootController.getXXX(java.lang.String,java.lang.String,java.lang.String,java.util.Optional<java.lang.String>)

引用

https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/autoconfigure/web/servlet/WebMvcTest.html

https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-testing-autoconfigured-mvc-tests