我正在使用Mockito来测试Spring Exception Handler。
这是测试代码:
@RunWith(MockitoJUnitRunner.class)
public class RestControllerTest {
@Mock
private InputValidationService inputValidationService;
@InjectMocks
private RestController controller;
private MockMvc mockMvc;
private List<String> errors;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(controller)
.setHandlerExceptionResolvers(withExceptionControllerAdvice())
.setMessageConverters(new MappingJackson2HttpMessageConverter()).build();
}
private ExceptionHandlerExceptionResolver withExceptionControllerAdvice() {
final ExceptionHandlerExceptionResolver exceptionResolver = new ExceptionHandlerExceptionResolver() {
@Override
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(final HandlerMethod handlerMethod,
final Exception exception) {
Method method = new ExceptionHandlerMethodResolver(RestControllerAdvice.class).resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(new RestControllerAdvice(), method);
}
return super.getExceptionHandlerMethod(handlerMethod, exception);
}
};
exceptionResolver.afterPropertiesSet();
return exceptionResolver;
}
@Test
public void thatExceptionHappens() throws Exception {
Set<ConstraintViolation<?>> violations = new HashSet<>();
doThrow(new ConstraintViolationException(violations)).when(inputValidationService).validateInput(anyString());
when(errorHelper.getErrorCode(anyString())).thenReturn("1000");
try {
mockMvc.perform(get("/employee/details/9876")).andDo(print()).andExpect(status().is(400));
} catch (Exception e) {
System.out.print("");
}
}
}
下面是控制器代码:
@ControllerAdvice
public class RestControllerAdvice {
@Resource
private ErrorHandler errorHandler;
@ExceptionHandler(value = { ConstraintViolationException.class })
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ResponseBody
public ServiceError handleConstraintViolations(ConstraintViolationException e) {
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
return errorHandler.buildResponseFromViolations(violations);
}
}
当我运行测试代码时,它会抛出异常,执行转到控制器建议,即 RestControllerAdvice(如预期的那样)
(我使用调试器检查过)
现在这是问题:
异常处理程序,即handleConstraintViolations()正确地组成了ErrorBody,但是当执行回到测试时,它实际上是一个异常而不是ErrorBody
。下面是堆栈跟踪,可能会让您了解正在发生的事情。
Failed to invoke @ExceptionHandler method: public ErrorBody RestControllerAdvice.handleConstraintViolations(javax.validation.ConstraintViolationException)
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:251)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:153)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:165)
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:81)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:126)
at org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver.doResolveHandlerMethodException(ExceptionHandlerExceptionResolver.java:363)
at org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver.doResolveException(AbstractHandlerMethodExceptionResolver.java:60)
at org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.resolveException(AbstractHandlerExceptionResolver.java:137)
at org.springframework.web.servlet.handler.HandlerExceptionResolverComposite.resolveException(HandlerExceptionResolverComposite.java:74)
at org.springframework.web.servlet.DispatcherServlet.processHandlerException(DispatcherServlet.java:1185)
at org.springframework.test.web.servlet.TestDispatcherServlet.processHandlerException(TestDispatcherServlet.java:109)
at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1022)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:973)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:895)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:858)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:687)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843)
at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:62)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:170)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:137)
at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:141)
at RestControllerTest.thatExceptionHappens(RestControllerTest.java:90)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
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.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
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.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:119)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
Could not complete request
javax.validation.ConstraintViolationException
at RestController.validateInput(RestController.java:46)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:832)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:743)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:961)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:895)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:858)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:687)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843)
at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:62)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:170)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:137)
at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:141)
at RestControllerTest.thatExceptionHappens(RestControllerTest.java:90)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
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.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
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.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:119)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
所以基本上当执行以下代码行时:
mockMvc.perform(get("/employee/details/9876")).andDo(print()).andExpect(status().is(400));
这个特定的行不应该抛出异常,异常和所有应该由控制器建议处理,它应该返回错误正文
根据下面的答案,以下是我找到的一些附加信息:
requestMediaTypes has the following "*/*"
supportedMediaTypes has the following
application/octet-stream
text/plain/;charset=ISO-8859
application/xml
text/xml
application/x-form-urlencoded
multipart/form-date
*/*
producibleMediaTypes has the following
application/json;charset=UTF-8
compatibleMediaTypes has the following:
application/json;charset=UTF-8
关联的消息转换器列表:
0 = {ByteArrayHttpMessageConverter@2624}
1 = {StringHttpMessageConverter@2655}
2 = {SourceHttpMessageConverter@2665}
3 = {AllEncompassingFormHttpMessageConverter@2666}
现在我可以看到问题发生在以下情况下messageConverter1.canWrite(returnValueClass, selectedMediaType) 被执行,我猜他们都无法转换。
即使我可能已经接近了这个问题,我仍然不知道如何解决问题。 我需要在我的测试类的 setUp 方法中添加一些东西吗?
从异常处理程序返回的响应必须适合请求的"接受"标头。如果没有,您将获得所描述的异常。Spring 还会考虑一些默认值,如果未设置"接受"标头。
几天前我遇到了类似的问题,并尝试将 spring 配置为始终使用固定的内容类型(例如应用程序/json)进行响应,但结果证明它太复杂了。
在 的第 189 行设置断点
AbstractMessageConverterMethodProcessor#writeWithMessageConverters
在那里,您将看到requestMediaTypes,producibleMediaType和compatibleMediaTypes。这应该为您提供有关问题实际根源的更多信息。
编辑
此问题没有通用的解决方案。你需要调查一下,为什么春天会得出结论,没有可接受的媒体类型。所以我能给出的最好的建议是检查上述断点处的三个变量。
- requestMediaType - 请求"接受"标头的媒体类型,如果未设置标头,则为默认值
- producibleMediaType - 控制器方法可以生成的媒体类型。
- 兼容媒体类型 - 上述媒体类型,Spring 认为兼容
如果兼容的媒体类型不为空或为空,则当 spring 尝试序列化您的 ServiceError 时,可能会发生错误。因此,请检查您那里是否有正确的注释,需要getter/setter id....
如果兼容的媒体类型为空,则您具有真正不兼容的媒体类型,您必须更改它。
* 我如何解决我的问题 *
当客户端请求的媒体类型只是拼写错误或我的控制器不支持并且我尝试返回 JSON 错误响应时,我的控制器会引发此异常。在这种情况下,不返回 JSON 响应是有道理的,因为客户端无论如何都无法接受它。所以我只返回HTTP状态400(错误请求),根本没有内容。