rest - 控制器测试失败并出现 MethodArgumentConversionNotSupportedException 但不在正在运行的应用程序中
问题描述
Spring Boot App 使用 2.0.3.RELEASE 的 Spring Boot。
所以我有这样写的 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:
因此,在测试中打开调试模式后,我得到以下输出:这让我越来越倾向于其中存在错误的方向......因为参数总是转换为字符串而不是可选......并且基于它的参数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
解决方案
这是您如何加载测试的问题。
当您使用它指定 @SpringBootTest(classes = RootController.class)
一个类时,它只SpringBootTest
会将该类加载到上下文中,即它允许您指定某些配置等,您希望为某些集成测试进行测试,而不是使用.ContextConfiguration
您可以删除RootController
并加载完整的测试应用程序上下文,从而有效地加载整个应用程序。
或者只是指定,
@RunWith(SpringRunner.class)
@WebMvcTest
public class RootControllerTest {
加载一个切片测试,它将只加载所需的 bean 来完全测试 WebMVC。
工作测试,
https://github.com/Flaw101/mockmvctests
编辑,
我已经更新了我的示例并引入了第二个控制器,但只加载RootController
了RootControllerMock
via @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>)
参考,
推荐阅读
- java - Intellij IDEA 2018.2 无法识别带有 JDK 10 的 JavaFX 包
- python - OpenCV中Trackbar的负值范围
- python - 在 windows 10 上安装 cocos2d-x 而不将 python 路径设置为旧版本
- javascript - rect.set 悬停区域不更新
- python - 我需要定义一个函数来确定单词之间的匹配字母并以整数形式返回
- apache-spark - 如何在 scala 中使用 spark cassandra 连接器 API
- python - 在 Python 中向球体表面添加值的最快方法是什么?
- bash - 在 Ubuntu 中读取 .bashrc 的 Cron 任务
- c# - 在 Xamarin Forms 中获取缓存图像源的原始高度和宽度
- r - 我正在尝试在 Y 上的 X 上绘制一个简单的图表。但是,即使数据值从 0 开始,X 值也不会从 Y 截距开始