2

在 Spring 3.0.5 及之前的版本中,可以创建一个基本控制器测试类,该类从 Spring 应用程序上下文中获取 HandlerMapping 对象,以通过 URL 直接访问和调用控制器的方法。我发现这种方法非常棒,因为除了测试控制器的方法之外,我还在单元/集成测试中测试了路径变量等。

在 Spring 3.2.4 中,由于 Spring 如何处理 Url 映射的重组,这种方法似乎不再可行。我看到 Spring 提供了一个新的 MVC 测试框架,但老实说,我认为它的设计过于冗长,看起来不像框架的其余部分或我的应用程序代码。它也不能很好地与 IntelliJ 中的智能感知功能配合使用。老实说,我宁愿不使用它。

那么,是否有另一种方法来测试不使用新的 Spring MVC 测试框架的控制器 URL,就像我以前做的那样?我有一个包含 371 个控制器测试的现有项目,我真的很想避免将所有内容迁移到使用 Spring MVC 测试框架。

这是handle()我使用 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);
}

这是一些测试代码,说明了我如何使用该handle()方法:

@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());
}

这是我的 servlet 应用程序上下文:

<?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>
4

1 回答 1

1

基本上你的测试方法从一开始就存在缺陷。总有可能存在超过 1 个HandlerMapping和 1 个 HandlerAdapter。你基本上在做的是模仿 DispatcherServlet。

您应该做的是查找所有HandlerMappings 和HandlerAdapters 并检查其中一个是否与 URL 匹配(即返回 HandlerExecutionChain)并选择适当的HandlerAdapter(调用supports方法)。你在做什么基本上就是在DispatcherServlet做什么。

于 2013-09-26T06:55:03.053 回答