【发布时间】:2016-07-09 10:07:46
【问题描述】:
我正在使用 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);
}
}
当我运行测试代码时,它会抛出异常并执行到 ControllerAdvice,即 RestControllerAdvice(如预期)
(我使用调试器检查过)
现在问题来了:
异常处理程序,即 handleConstraintViolations() 正确地组成了 ErrorBody,但是当执行返回到测试时,它实际上是一个 Exception 而不是一个 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));
此特定行不应引发异常,该异常应由 ControllerAdvice 处理并返回 ErrorBody
根据下面的答案,我发现了一些附加信息:
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 方法中添加一些东西吗?
【问题讨论】:
标签: java spring unit-testing spring-mvc mockito