Spring-test 集成测试中的自动装配 HttpServletRequest
Autowired HttpServletRequest in Spring-test integration tests
我正在尝试进行测试以涵盖登录功能。 Spring 的版本是 3.2.12。我有一个 session 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
}
这在服务器上运行时完美,但是当运行通过spring-test时,问题就来了。这是我的测试方法:
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).addFilter(springSecurityFilterChain).build();
mockMvc.perform(post(URL));
调试后发现,当测试spring上下文启动时,在
ServletTestExecutionListener.setUpRequestContextIfNecessary
创建一个 MockHttpServletRequest 实例,
MockHttpServletRequest 请求 = new MockHttpServletRequest(mockServletContext);
// 让我们称这个实例为 A。
这是到处注入的实例,我使用
@Autowired
HttpServletRequest request;
然而,调用 MockMvc.perform,创建了另一个 MockHttpServletRequest 实例(我们称之为实例 B),它被传递给所有过滤器、servlet 等。所以,基本上,我在过滤器中设置的属性在请求中,无法在 ClientSessionServiceImpl 中读取,因为那里注入了不同的 MockHttpServletRequest 实例。
我花了很多时间在这上面,但仍然没有找到解决方案。
P.S。
我搜索了 Whosebug,有类似标题的问题,但描述的问题与我的不同,因为我不想将 HttpServletRequest 作为参数传递,并且宁愿让它自动连接,除非有充分的理由.
您可以使用 RequestPostProcessor 换出在 perform 中创建的请求。您可以在测试中添加一个 util 方法,例如
private static RequestPostProcessor mockedRequest(final MockHttpServletRequest mockHttpServletRequest) {
return new RequestPostProcessor() {
@Override
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
return mockHttpServletRequest;
}
};
}
您可以通过 with
方法添加此 post 处理器来应用它
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).addFilter(springSecurityFilterChain).build();
mockMvc.perform(post(URL).with(mockedRequest(request)));
此处传递给 mockedRequest
方法的 request
将是包含所需属性的原始请求
So, basically, the attribute i set in the filter in the request, can't
be read in the ClientSessionServiceImpl, because different instance of
MockHttpServletRequest is injected there.
这是关于何时将 Spring 的 RequestAttributes
填充到 RequestContextHolder
的时间问题。在生产中,我假设您正在配置 RequestContextFilter
或 RequestContextListener
.
无论如何,在您的测试中手动将 RequestContextFilter
的实例添加到过滤器链的前面将解决问题。
mockMvc = MockMvcBuilders
.webAppContextSetup(this.wac)
.addFilters(new RequestContextFilter(), testFilterChain)
.build();
请注意,这将成为 Spring Framework 4.2 中的默认行为:模拟 RequestContextFilter
的代码将直接在 MockMvc
中实现。有关详细信息,请参阅 JIRA 问题 SPR-13217。
顺便说一句,不支持配置由 ServletTestExecutionListener
创建的 MockHttpServletRequest
。如果您使用的是 MockMvc
,则需要通过 RequestBuilders
.
配置模拟请求
然而,话虽如此,如果您确实需要手动修改 ServletTestExecutionListener
创建的模拟请求,然后将其重新用于 MockMvc
,您可以创建以下 class 在你的项目中:
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);
}
}
注意:必须在org.springframework.test.web.servlet.request
包中;否则,它无法扩展所需的 MockHttpServletRequestBuilder
。
然后,使用 PatchedMockHttpServletRequestBuilder
中的 get()
方法而不是 MockMvcRequestBuilders
中的方法,一切都会如您所愿!
显然,上面的例子重新实现了 get()
,但你自然可以对post()
等做同样的事情
仅供参考:我最终可能会将 createServletRequest()
的上述补丁版本提交给 Spring Framework 4。2.x(参见 JIRA 问题 SPR-13211)。
此致,
Sam(Spring TestContext Framework 的作者)
我有一个类似的问题,我在我的休息控制器中自动装配了 HttpServletRequest 和 HttpServletResponse
@Autowired protected HttpServletRequest httpRequest;
@Autowired protected HttpServletResponse httpResponse;
然而,当我尝试使用以下配置使用 spring 测试时,由于测试范围无法自动装配 httpRequest
,测试失败。
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("classpath:spring-config-unit-test.xml")
搜索网络后的解决方案我将模拟请求和响应声明为我的 bean 定义(单元测试)xml 的第一行,如下所示。希望这对某些人有所帮助
<bean class="org.springframework.mock.web.MockHttpServletRequest" name="httpRequest" lazy-init="false" />
<bean class="org.springframework.mock.web.MockHttpServletResponse" name="httpResponse" lazy-init="false" />
试试这个配置:
@RunWith(SpringJUnit4ClassRunner.class)
public class MyTests extends AbstractContextControllerTests {
@Test
public void test() {
}
}
其中 AbstractContextControllerTests 是这样的:
@WebAppConfiguration
@ContextConfiguration(classes = {DispatcherConfig.class}, loader = AnnotationConfigWebContextLoader.class)
public class AbstractContextControllerTests {
@Autowired
protected WebApplicationContext wac;
}
而DispatherConfig是这样的:
@Configuration
@EnableWebMvc
public class DispatcherConfig extends WebMvcConfigurerAdapter {
}
我正在尝试进行测试以涵盖登录功能。 Spring 的版本是 3.2.12。我有一个 session 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
}
这在服务器上运行时完美,但是当运行通过spring-test时,问题就来了。这是我的测试方法:
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).addFilter(springSecurityFilterChain).build();
mockMvc.perform(post(URL));
调试后发现,当测试spring上下文启动时,在 ServletTestExecutionListener.setUpRequestContextIfNecessary 创建一个 MockHttpServletRequest 实例, MockHttpServletRequest 请求 = new MockHttpServletRequest(mockServletContext); // 让我们称这个实例为 A。 这是到处注入的实例,我使用
@Autowired
HttpServletRequest request;
然而,调用 MockMvc.perform,创建了另一个 MockHttpServletRequest 实例(我们称之为实例 B),它被传递给所有过滤器、servlet 等。所以,基本上,我在过滤器中设置的属性在请求中,无法在 ClientSessionServiceImpl 中读取,因为那里注入了不同的 MockHttpServletRequest 实例。
我花了很多时间在这上面,但仍然没有找到解决方案。
P.S。 我搜索了 Whosebug,有类似标题的问题,但描述的问题与我的不同,因为我不想将 HttpServletRequest 作为参数传递,并且宁愿让它自动连接,除非有充分的理由.
您可以使用 RequestPostProcessor 换出在 perform 中创建的请求。您可以在测试中添加一个 util 方法,例如
private static RequestPostProcessor mockedRequest(final MockHttpServletRequest mockHttpServletRequest) {
return new RequestPostProcessor() {
@Override
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
return mockHttpServletRequest;
}
};
}
您可以通过 with
方法添加此 post 处理器来应用它
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).addFilter(springSecurityFilterChain).build();
mockMvc.perform(post(URL).with(mockedRequest(request)));
此处传递给 mockedRequest
方法的 request
将是包含所需属性的原始请求
So, basically, the attribute i set in the filter in the request, can't be read in the ClientSessionServiceImpl, because different instance of MockHttpServletRequest is injected there.
这是关于何时将 Spring 的 RequestAttributes
填充到 RequestContextHolder
的时间问题。在生产中,我假设您正在配置 RequestContextFilter
或 RequestContextListener
.
无论如何,在您的测试中手动将 RequestContextFilter
的实例添加到过滤器链的前面将解决问题。
mockMvc = MockMvcBuilders
.webAppContextSetup(this.wac)
.addFilters(new RequestContextFilter(), testFilterChain)
.build();
请注意,这将成为 Spring Framework 4.2 中的默认行为:模拟 RequestContextFilter
的代码将直接在 MockMvc
中实现。有关详细信息,请参阅 JIRA 问题 SPR-13217。
顺便说一句,不支持配置由 ServletTestExecutionListener
创建的 MockHttpServletRequest
。如果您使用的是 MockMvc
,则需要通过 RequestBuilders
.
然而,话虽如此,如果您确实需要手动修改 ServletTestExecutionListener
创建的模拟请求,然后将其重新用于 MockMvc
,您可以创建以下 class 在你的项目中:
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);
}
}
注意:必须在org.springframework.test.web.servlet.request
包中;否则,它无法扩展所需的 MockHttpServletRequestBuilder
。
然后,使用 PatchedMockHttpServletRequestBuilder
中的 get()
方法而不是 MockMvcRequestBuilders
中的方法,一切都会如您所愿!
显然,上面的例子重新实现了 get()
,但你自然可以对post()
等做同样的事情
仅供参考:我最终可能会将 createServletRequest()
的上述补丁版本提交给 Spring Framework 4。2.x(参见 JIRA 问题 SPR-13211)。
此致,
Sam(Spring TestContext Framework 的作者)
我有一个类似的问题,我在我的休息控制器中自动装配了 HttpServletRequest 和 HttpServletResponse
@Autowired protected HttpServletRequest httpRequest;
@Autowired protected HttpServletResponse httpResponse;
然而,当我尝试使用以下配置使用 spring 测试时,由于测试范围无法自动装配 httpRequest
,测试失败。
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("classpath:spring-config-unit-test.xml")
搜索网络后的解决方案我将模拟请求和响应声明为我的 bean 定义(单元测试)xml 的第一行,如下所示。希望这对某些人有所帮助
<bean class="org.springframework.mock.web.MockHttpServletRequest" name="httpRequest" lazy-init="false" />
<bean class="org.springframework.mock.web.MockHttpServletResponse" name="httpResponse" lazy-init="false" />
试试这个配置:
@RunWith(SpringJUnit4ClassRunner.class)
public class MyTests extends AbstractContextControllerTests {
@Test
public void test() {
}
}
其中 AbstractContextControllerTests 是这样的:
@WebAppConfiguration
@ContextConfiguration(classes = {DispatcherConfig.class}, loader = AnnotationConfigWebContextLoader.class)
public class AbstractContextControllerTests {
@Autowired
protected WebApplicationContext wac;
}
而DispatherConfig是这样的:
@Configuration
@EnableWebMvc
public class DispatcherConfig extends WebMvcConfigurerAdapter {
}