Spring-test 集成测试中的自动连线 HttpServletRequest

Geo*_*ava 5 java spring spring-mvc spring-test mockmvc

我正在尝试进行测试以涵盖登录功能。Spring 的版本是 3.2.12。我有一个会话 bean,声明为:

@Service
@Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
public class ClientSessionServiceImpl implements ClientSessionService {
    @Autowired
    private HttpServletRequest request;
    // This method is called during the login routine from the filter
    public boolean checkUser() {
    // I rely on request attributes here, which were set in the filter
    }
Run Code Online (Sandbox Code Playgroud)

这在服务器上运行时效果很好,但是当使用spring-test的方式运行时,问题就来了。这是我的测试方法:

this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).addFilter(springSecurityFilterChain).build();
mockMvc.perform(post(URL));
Run Code Online (Sandbox Code Playgroud)

经过调试,我发现,当测试 spring 上下文启动时,在 ServletTestExecutionListener.setUpRequestContextIfNecessary 中创建了一个 MockHttpServletRequest 实例,MockHttpServletRequest request = new MockHttpServletRequest(mockServletContext);? // 让我们称这个实例为 A。这是到处注入的实例,我使用

@Autowired
HttpServletRequest request;
Run Code Online (Sandbox Code Playgroud)

而调用 MockMvc.perform 会创建另一个 MockHttpServletRequest 实例(我们称其为实例 B),该实例被传递给所有过滤器、servlet 等。因此,基本上,我在请求中的过滤器中设置的属性不能在 ClientSessionServiceImpl 中读取,因为在那里注入了不同的 MockHttpServletRequest 实例。

我花了很多时间在这上面,但仍然没有找到解决方案。

PS 我通过 StackOverflow 搜索,有类似标题的问题,但描述的问题与我的不同,因为我不想将 HttpServletRequest 作为参数传递,并且宁愿让它自动装配,除非有充分的理由它。

Sam*_*nen 2

因此,基本上,我在请求的过滤器中设置的属性无法在 ClientSessionServiceImpl 中读取,因为那里注入了 MockHttpServletRequest 的不同实例。

这是一个关于 Spring 何时RequestAttributes填充到RequestContextHolder. 在生产中,我假设您正在配置 aRequestContextFilter或 a RequestContextListener

无论如何,RequestContextFilter在测试中手动将 的实例添加到过滤器链的前面将解决问题。

mockMvc = MockMvcBuilders
  .webAppContextSetup(this.wac)
  .addFilters(new RequestContextFilter(), testFilterChain)
  .build();
Run Code Online (Sandbox Code Playgroud)

请注意,这将成为 Spring Framework 4.2 中的默认行为:模拟的代码RequestContextFilter将直接在MockMvc. 有关详细信息,请参阅 JIRA 问题SPR-13217


顺便说一句,不支持配置MockHttpServletRequest由 创建的。ServletTestExecutionListener如果您正在使用MockMvc,则需要通过RequestBuilders.

然而,话虽如此,如果您确实需要修改ServletTestExecutionListener手动创建的模拟请求,然后将其重新使用MockMvc,则可以在项目中创建以下类:

package org.springframework.test.web.servlet.request;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;

import org.springframework.http.HttpMethod;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * Patched version of {@link MockHttpServletRequestBuilder}.
 *
 * @author Sam Brannen
 * @since 4.2
 */
public class PatchedMockHttpServletRequestBuilder extends MockHttpServletRequestBuilder {

    public static MockHttpServletRequestBuilder get(String urlTemplate, Object... urlVariables) {
        return new PatchedMockHttpServletRequestBuilder(HttpMethod.GET, urlTemplate, urlVariables);
    }

    public PatchedMockHttpServletRequestBuilder(HttpMethod httpMethod, String urlTemplate, Object... urlVariables) {
        super(httpMethod, urlTemplate, urlVariables);
    }

    /**
     * Create a {@link MockHttpServletRequest}.
     * <p>If an instance of {@code MockHttpServletRequest} is available via
     * the {@link RequestAttributes} bound to the current thread in
     * {@link RequestContextHolder}, this method simply returns that instance.
     * <p>Otherwise, this method creates a new {@code MockHttpServletRequest}
     * based on the supplied {@link ServletContext}.
     * <p>Can be overridden in subclasses.
     * @see RequestContextHolder#getRequestAttributes()
     * @see ServletRequestAttributes
     */
    @Override
    protected MockHttpServletRequest createServletRequest(ServletContext servletContext) {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes instanceof ServletRequestAttributes) {
            HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
            if (request instanceof MockHttpServletRequest) {
                return (MockHttpServletRequest) request;
            }
        }

        return new MockHttpServletRequest(servletContext);
    }

}
Run Code Online (Sandbox Code Playgroud)

注意:必须org.springframework.test.web.servlet.request包装内;否则,它无法扩展MockHttpServletRequestBuilder所需的内容。

然后,使用get()fromPatchedMockHttpServletRequestBuilder而不是 from方法MockMvcRequestBuilders,一切都应该按您的预期工作!

显然,上面的示例重新实现了 get(),但是您自然可以对 等执行相同的操作post()

仅供参考:我最终可能会将上述修补版本提交createServletRequest()到 Spring Framework 4.2.x(请参阅 JIRA 问题SPR-13211)。

问候,

Sam(Spring TestContext 框架的作者