@ExceptionHandler not working with Spring MVC 3.1 unit test

旧时模样 提交于 2019-12-10 21:03:11

问题


I'm able to do nearly all of the unit test on my Spring MVC controller by instantiating the object outside of the normal servlet context. But I would like to be able to run some tests to insure that my object serialization is working properly, headers are being generated, etc.

To run a test inside the servlet context, I create a modified context file so that various beans are NOT constructed, then I create mocked versions of those beans in my test case using EasyMock. I then invoke my handler with code like this, and get most of what I need from the MockHttpServletResponse.

I think this gets the essence of it:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "file:root-context.xml",
                                    "file:junit-servlet-context.xml" } )

public class HomeControllerConfigHandlerHttp {
@Autowired
private RequestMappingHandlerAdapter handlerAdapter;

@Autowired
private RequestMappingHandlerMapping handlerMapping;
    ... //miscellaneous setup
public void someTest() throws Exception
{
    MockHttpServletRequest request = new MockHttpServletRequest();
    request.setMethod("GET");
    request.setRequestURI("/config");
    request.addParameter("foo", "bar");
    request.addParameter("device", "oakmont");
    MockHttpServletResponse response = new MockHttpServletResponse();
    Object handler = handlerMapping.getHandler(request).getHandler();
    replay(dblient);
    expect(serviceClient.checkDevice("oakmont")).andReturn( true );
    serviceClient.destroy();
    replay(serviceClient);
    ModelAndView modelAndView = handlerAdapter.handle(request, response, handler);
    String content = new String( response.getContentAsByteArray() );
    Assert.assertEquals(content, "Expected configuration");
    String content_type = response.getHeader("Content-type");
    Assert.assertEquals( content_type, "text/plain");
    int status = response.getStatus();
    Assert.assertEquals(status,  200 );

This does what I expect it to do, but there is one catch. I do a lot of error handling in my controller with an @ExceptionHandler. It is an easy way to back out of an error case in any handler, and it gives me a consistent way of exposing errors.

The @ExceptionHandler works fine in normal servlet deployment, but in this unit test mockup it does not get invoked when I throw an exception. Stepping into the Spring code is a bit of a challenge for me, I'm new to it and so I get lost pretty quickly. However, it looks like in the normal servlet environment, there is an exception handler that looks for an annotated handler. When running under SpringJUnit4ClassRunner, the exception isn't processed the same way.

If there is a way to fix this, I'd like to do it. I've avoided spring-test-mvc because of a lack of pioneer spirit, but if someone tells me that it can manage this just fine, I'll try that instead.

The contents of my junit-servlet-context.xml file are nearly identical to the servlet-context.xml file created by the Spring Template MVC wizard. The only difference is the addition of an exclude-filter that is used to prevent instantiation of a @Component that creates a few singletons used by my controller.

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->

<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />

<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />

<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <beans:property name="prefix" value="/WEB-INF/views/" />
    <beans:property name="suffix" value=".jsp" />
</beans:bean>
<context:component-scan base-package="com.cisco.onplus.home.dmz" >
<context:exclude-filter type="regex" expression=".*InitDatabase.*"/>
</context:component-scan>   
</beans:beans>

回答1:


Add following class for loading Dispatcher servlet in context:

public class MockWebApplicationContext extends AbstractContextLoader {

@Override
public ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration) throws Exception {
    String[] locations = mergedContextConfiguration.getLocations();
    return loadContext(locations);
}

@Override
public ApplicationContext loadContext(String... locations) throws Exception {

    XmlWebApplicationContext webApplicationContext = new XmlWebApplicationContext();
    webApplicationContext.setConfigLocations(locations);
    webApplicationContext.setServletContext(new MockServletContext(new FileSystemResourceLoader() ) );

    ServletConfig config = new MockServletConfig();
    config.getServletContext().setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, webApplicationContext);

    final DispatcherServlet servlet = new DispatcherServlet(webApplicationContext);

    webApplicationContext.addBeanFactoryPostProcessor(new BeanFactoryPostProcessor() {
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
            beanFactory.registerResolvableDependency(DispatcherServlet.class, servlet);
        }
    });

    webApplicationContext.refresh();
    servlet.init(config);

    return webApplicationContext;
}

@Override
protected String getResourceSuffix() {
    return ".xml";
}

}

Once you are done with that use

@ContextConfiguration(locations = {"classpath:app-config.xml",loader = MockWebApplicationContext.class)

to load DispatcherServlet using Autowired annotation on test class. Process it using

servlet.service(request,response); 

Now it should handle exception flow also.

It probably requires 3.1.2 though.




回答2:


I am afraid you will have to look at spring-test-mvc, the reason is the handling of exception from the controllers and using ExceptionResolver to call the appropriate @ExceptionHandler is done at the level of DispatcherServlet, not HandlerAdapter. The test that you have starts at the level of HandlerAdapter.

I highly recommend spring-test-mvc though, I have been using it for a while and have not seen any issues for my scenarios - http://biju-allandsundry.blogspot.com/2012/07/spring-mvc-integration-tests.html.

A test for an exception flow would look like this with spring-test-mvc:

xmlConfigSetup("classpath:/META-INF/spring/web/webmvc-config.xml")
    .configureWebAppRootDir("src/main/webapp", false).build()
    .perform(get("/contexts/exception"))
    .andExpect(status().isOk())
    .andExpect(view().name("exceptionPage"));


来源:https://stackoverflow.com/questions/11649036/exceptionhandler-not-working-with-spring-mvc-3-1-unit-test

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!