【问题标题】:Spring @ExceptionHandler test gets invoked but instead of error body it re-throws the Exception using MockitoSpring @ExceptionHandler 测试被调用,但不是错误主体,而是使用 Mockito 重新抛出异常
【发布时间】: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


    【解决方案1】:

    您从异常处理程序返回的响应必须适合请求的“接受”标头。如果没有,您将获得所描述的异常。 如果未设置“Accept”标头,Spring 还会考虑一些默认值。

    几天前我遇到了类似的问题,并尝试将 spring 配置为始终使用固定的 Content-Type(例如 application/json)作为响应,但结果太复杂了。

    在第 189 行设置断点

    AbstractMessageConverterMethodProcessor#writeWithMessageConverters

    在那里你会看到requestedMediaTypes、producibleMediaTypes 和 compatibleMediaTypes。这应该可以为您提供有关问题根源的更多信息。

    编辑

    这个问题没有通用的解决方案。 您需要调查一下,为什么 spring 会得出结论,即没有可接受的媒体类型。 所以我能给出的最好建议是检查上述断点处的三个变量。

    • requestedMediaTypes - 请求“接受”标头的媒体类型,如果标头未设置,则为默认值
    • producibleMediaTypes - 控制器方法可以产生的媒体类型。
    • compatibleMediaTypes - spring 认为兼容的上述媒体类型

    如果 compatibleMediaTypes 不为 null 或为空,则当 spring 尝试序列化您的 ServiceError 时可能会发生错误。所以检查你是否有正确的注释,需要getter/setter id....

    如果 compatibleMediaTypes 为 null,那么您的媒体类型确实不兼容,您必须更改它。

    * 我是如何解决我的问题的 *

    当客户端请求的媒体类型拼写错误或我的控制器不支持并且我尝试返回 JSON 错误响应时,我的控制器引发了此异常。 在这种情况下,不返回 JSON 响应是有道理的,因为客户端无论如何都无法接受它。所以我只返回 HTTP 状态 400 (Bad Request) 并且没有任何内容。

    【讨论】:

    • 很高兴您设法解决了您的问题。但是你是如何解决这个问题的。因为错误主体(我的响应)也应该以 JSON 格式返回。那么我到底应该在这里做什么呢。
    • 根据您的回答,我必须说这非常好。我缩小了异常重新抛出的真正意义,但我仍然不知道如何解决这个问题。我在我的设置中添加了一个消息转换器,它甚至没有出现在消息转换器列表中。
    猜你喜欢
    • 2021-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-21
    • 2019-03-06
    • 2014-07-27
    • 1970-01-01
    • 2017-12-04
    相关资源
    最近更新 更多