简体   繁体   中英

Alternative to testing controllers in Spring 3.2.4

In Spring 3.0.5 and before, it was possible to create a base controller test class that fetched the HandlerMapping object from the Spring Application Context to directly access and call into a controller's method via the URL. I found this approach to be really awesome because in addition to testing the controller's methods, I was also testing the path variables and such in my unit/integration tests.

With Spring 3.2.4, this approach appears not to be possible any longer due to the restructuring of how Spring deals with Url mappings. I see that Spring provides a new MVC test framework, but to be honest, I think it's design is too verbose and looks nothing like the rest of the framework or my application code. It also doesn't play nicely with intellisense features in IntelliJ. To be honest, I'd rather not use it.

So, is there an alternative way to test controller URL's that does not use the new Spring MVC test framework, like I was doing before? I have an existing project with 371 controller tests, and I'd REALLY like to avoid migrating everything over to use the Spring MVC test framework.

Here is the handle() method I was using to test controllers using Spring 3.0.5:

protected ModelAndView handle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    final HandlerMapping handlerMapping = applicationContext.getBean(HandlerMapping.class);
    final HandlerExecutionChain handler = handlerMapping.getHandler(request);
    assertNotNull("No handler found for request, check you request mapping", handler);

    final Object controller = handler.getHandler();

    final HandlerInterceptor[] interceptors = handlerMapping.getHandler(request).getInterceptors();

    for (HandlerInterceptor interceptor : interceptors) {
        final boolean carryOn = interceptor.preHandle(request, response, controller);
        if (!carryOn) {
            return null;
        }
    }

    return handlerAdapter.handle(request, response, controller);
}

protected ModelAndView handle(String method, String path, String queryString) throws Exception {
    request.setMethod(method);
    request.setRequestURI(path);

    if(queryString != null) {
        String[] parameters = queryString.split("&");
        for(String parameter : parameters) {
            String[] pair = parameter.split("=");
            if(pair.length == 2) {
                request.setParameter(pair[0], pair[1]);
            } else {
                request.setParameter(pair[0], "");
            }
        }
    }

    return handle(request, response);
}

protected ModelAndView handle(String method, String path, String attribute, Object object) throws Exception {
    MockHttpSession session = new MockHttpSession();
    session.setAttribute(attribute, object);
    request.setSession(session);

    return handle(method, path, null);
}

protected ModelAndView handle(String method, String path) throws Exception {
    return handle(method, path, null);
}

Here is some test code illustrating how I was using the handle() method:

@Test
public void show() throws Exception {
    ModelAndView modelAndView = handle("GET", "/courseVersion/1/section/1");

    Section section = (Section) modelAndView.getModel().get("section");

    assertEquals(1, section.getId());
}

Here is my servlet application context:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:security="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
            http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">

    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations" value="classpath:applicationContext.properties"/>
    </bean>

    <bean id="expressionHandler"
          class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
    </bean>

    <security:global-method-security pre-post-annotations="enabled">
        <security:expression-handler ref="expressionHandler"/>
    </security:global-method-security>

    <context:component-scan base-package="keiko.web.controllers"/>

    <mvc:annotation-driven validator="validator" />
    <mvc:interceptors>
        <bean class="keiko.web.interceptors.IpValidationInterceptor" />
        <bean class="keiko.web.interceptors.UnreadMessagesInterceptor" />
        <bean class="keiko.web.interceptors.ThemeInterceptor" />
        <bean class="keiko.web.interceptors.ApplicationMenuInterceptor" />
    </mvc:interceptors>

    <bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
        <property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
        <property name="freemarkerSettings">
            <props>
                <prop key="auto_import">lib/common.ftl as common, lib/layouts.ftl as layouts</prop>
                <prop key="whitespace_stripping">true</prop>
            </props>
        </property>
        <property name="freemarkerVariables">
            <map>
                <entry key="template_update_delay" value="0"/>
                <entry key="default_encoding" value="ISO-8859-1"/>
                <entry key="number_format" value="0.##"/>
                <entry key="xml_escape">
                    <bean class="freemarker.template.utility.XmlEscape"/>
                </entry>
            </map>
        </property>
    </bean>

    <bean id="contentNegotiatingViewResolver"
          class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
        <property name="order" value="1"/>
        <property name="ignoreAcceptHeader" value="true" />
        <property name="defaultContentType" value="text/html" />
        <property name="mediaTypes">
            <map>
                <entry key="html" value="text/html"/>
                <entry key="json" value="application/json"/>
            </map>
        </property>
        <property name="useNotAcceptableStatusCode" value="true" />
        <property name="viewResolvers">
            <list>
                <bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
                    <property name="contentType" value="text/html" />
                    <property name="order" value="2"/>
                    <property name="cache" value="${freemarker.cache}"/>
                    <property name="prefix" value=""/>
                    <property name="suffix" value=".ftl"/>
                    <property name="exposeSpringMacroHelpers" value="true"/>
                </bean>
            </list>
        </property>
        <property name="defaultViews">
            <list>
                <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView">
                    <property name="contentType" value="application/json" />
                </bean>
            </list>
        </property>
    </bean>

    <bean id="viewNameTranslator" class="org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator"/>

    <bean id="multipartResolver"
          class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="1000000"/>
    </bean>

    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <prop key="keiko.domain.courseauthor.SectionIsDelayedException">error/sectionIsDelayed</prop>
                <prop key="keiko.service.director.CompanyHomepageClosedException">error/registrationClosed</prop>
                <prop key="keiko.service.director.IpDeniedException">error/ipDenied</prop>
            </props>
        </property>
    </bean>

</beans>

Basically your test method has been flawed basically from the start. There was always a possibility that there where more then 1 HandlerMapping and 1 HandlerAdapter. What you are basically doing is mimic the DispatcherServlet.

What you should do is lookup all HandlerMapping s and HandlerAdapter s and check if one of them has a match for the URL (ie returning a HandlerExecutionChain) and select the appropriate HandlerAdapter (calling the supports method). What you are doing is basically what the DispatcherServlet is doing.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM