【问题标题】:Alternative to testing controllers in Spring 3.2.4在 Spring 3.2.4 中测试控制器的替代方法
【发布时间】:2013-09-26 01:44:55
【问题描述】:

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

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

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

这是我使用 Spring 3.0.5 测试控制器时使用的 handle() 方法:

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>

【问题讨论】:

  • 你的bean是什么HandlerMapping类型?
  • 在 3.0.5 中,我不确定。它只有一个实例化。正如您在代码中看到的那样,您可以通过此处访问应用程序中的每个控制器。当然,它会在 Spring 3.2.4 中中断。
  • 这就是他们引入 MVC 测试框架的原因,因此您无需依赖“幕后”的东西来测试您的控制器。当您更改此内容时会发生什么,然后在 Spring 4 中,他们再次在幕后更改内容?就个人而言,我只会使用提供的工具...
  • 我不会说它设计得很糟糕,而是故意这样设计的……它是一个流畅的api! :) 当然,如果你不喜欢流利的 api,那可能会有问题。
  • @egervari 我刚刚使用 springs 测试框架编写了 450 个测试,完全没有反直觉的东西。我只使用了四种静态方法,它们是不同的请求类型:get、post、delete、put。在不使用现有模拟的情况下创建自己独特的测试框架更加违反直觉。顺便说一句,这完全是错误的:“在 99% 的情况下,真正真正需要的是带有返回 ModelAndView 对象的请求方法的 url 调用。”

标签: java spring unit-testing spring-mvc controller


【解决方案1】:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-24
    • 2015-02-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多