5

I am trying to do a test to cover login functionality. Version of Spring is 3.2.12. I have a session bean, declared as:

@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
    }

This works perfectly when run on the server, but when run with the means of spring-test, the problem comes. This is my test method:

this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).addFilter(springSecurityFilterChain).build();
mockMvc.perform(post(URL));

After debugging, i found out, that when test spring context is started, in the ServletTestExecutionListener.setUpRequestContextIfNecessary an instance of MockHttpServletRequest is created, MockHttpServletRequest request = new MockHttpServletRequest(mockServletContext);
 // Lets' call this instance A. And this is the instance that's get injected everywhere, i use

@Autowired
HttpServletRequest request;

Whereas, calling MockMvc.perform, creates another instance of MockHttpServletRequest (Let's call it instance B), which is passed to all the filters, servlets, etc. 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.

I spent bunch of time on this, but still have not found the solution.

P.S. I searched through StackOverflow, there are questions with similar titles, but describing the problems that differ from mine, as i don't want to pass HttpServletRequest as a parameter, and would prefer to have it Autowired, unless there is a good reason for it.

George Lezhava
  • 53
  • 1
  • 1
  • 3
  • 1
    I know this does not address your problem; however, as an aside: why is your service session-scoped instead of request-scoped since its state is tied to the request and not the session? – Sam Brannen Jul 02 '15 at 20:14
  • I assume if you only access the request attributes upon the first request when the session is initialized, then you won't have any issues, but otherwise I suspect you would experience potentially strange behavior. – Sam Brannen Jul 02 '15 at 20:15
  • 1
    Regarding your actual problem, this is a current limitation of the integration between the _Spring TestContext Framework_ and the _Spring MVC Test Framework_. I unfortunately do not currently have a clean solution for you, but we are in fact looking into this issue. – Sam Brannen Jul 02 '15 at 20:17
  • Out of curiosity, what happens if you register Spring's `RequestContextFilter` like the following? `webAppContextSetup(wac).addFilters(new RequestContextFilter(), springSecurityFilterChain).build()` – Sam Brannen Jul 07 '15 at 21:27
  • I only pasted small part of ClientSessionServiceImpl, i actually need it to be session scope, and anyways, changing it to "request" does not solve the issue. – George Lezhava Jul 10 '15 at 10:41
  • Adding new RequestContextFilter() to the MockMvc really helped! The test works, though this solution seems to be similar, but a bit cleaner, as the one i used in https://github.com/georgespear/SpringTestScenario/blob/master/src/test/java/com/springintegration/sample/test/HackOfControllerITTestCase.java – George Lezhava Jul 10 '15 at 10:45

4 Answers4

2

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.

This is a timing issue with regard to when Spring's RequestAttributes are populated in the RequestContextHolder. In production I would assume that you are configuring either a RequestContextFilter or a RequestContextListener.

In any case, manually adding an instance of RequestContextFilter to the front of the filter chain in your test will solve the problem.

mockMvc = MockMvcBuilders
  .webAppContextSetup(this.wac)
  .addFilters(new RequestContextFilter(), testFilterChain)
  .build();

Please note that this will become the default behavior in Spring Framework 4.2: code that simulates RequestContextFilter will be implemented directly in MockMvc. See JIRA issue SPR-13217 for details.


As an aside, configuring the MockHttpServletRequest that is created by the ServletTestExecutionListener is not supported. If you are using MockMvc it is expected that you configure the mocked request via the RequestBuilders.

However, having said that, if you have a concrete need for modifying the mock request created by ServletTestExecutionListener manually and then having that re-used with MockMvc, you can create the following class in your project:

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

}

Note: it must be in the org.springframework.test.web.servlet.request package; otherwise, it can't extend MockHttpServletRequestBuilder which is required.

Then, use the get() method from PatchedMockHttpServletRequestBuilder instead of from MockMvcRequestBuilders, and everything should work as you expect!

Obviously, the above example re-implements get(), but you can naturally do the same for post(), etc.

FYI: I might eventually commit the above patched version of createServletRequest() to Spring Framework 4.2.x (see JIRA issue SPR-13211).

Regards,

Sam (author of the Spring TestContext Framework)

Sam Brannen
  • 29,611
  • 5
  • 104
  • 136
  • Thanks for the reply Sam, I saw in [SPR-13211](https://jira.spring.io/browse/SPR-13211) that you have problems with reproducing the issue, at least in the way i described it. So i made a small sample project to illustrate it. You can check it here: https://github.com/georgespear/SpringTestScenario. It clearly shows the problem, the request instances are different in Filter and Service. – George Lezhava Jul 10 '15 at 10:14
  • Regarding the solution you proposed. Well, i thought about that as well, but creating local classes within org.springframework.test.web.servlet.request package is not the way i want to go, unless there are no others :) – George Lezhava Jul 10 '15 at 10:18
  • Did you try my other recommendation? What happens if you register Spring's `RequestContextFilter` as follows? `webAppContextSetup(wac).addFilters(new RequestContextFilter(), springSecurityFilterChain).build()` – Sam Brannen Jul 10 '15 at 10:23
  • Thanks for making that sample project available. I will check it out! – Sam Brannen Jul 10 '15 at 10:28
  • This solves your problem elegantly: `mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).addFilters(new RequestContextFilter(), testFilterChain).build();` – Sam Brannen Jul 10 '15 at 10:52
  • FYI: I reworked my answer to include actual solution using `RequestContextFilter`. – Sam Brannen Jul 10 '15 at 11:02
1

i had a similar issue where i had both HttpServletRequest and HttpServletResponse autowired in my rest controller

@Autowired protected HttpServletRequest httpRequest;
@Autowired protected HttpServletResponse httpResponse;

However when I try to use spring test using below configuration , test failed due to it unable to autowire httpRequest due to test scope .

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("classpath:spring-config-unit-test.xml")

Solution after searching web i declared the mock request and response as fist line of my bean definition(unit test) xml like below . hope this help some one

<bean class="org.springframework.mock.web.MockHttpServletRequest" name="httpRequest" lazy-init="false" />
<bean class="org.springframework.mock.web.MockHttpServletResponse" name="httpResponse" lazy-init="false" />
csf
  • 529
  • 1
  • 4
  • 16
0

You can use RequestPostProcessor to swap out the request created inside perform. You can add a util method to your test, something like

private static RequestPostProcessor mockedRequest(final MockHttpServletRequest mockHttpServletRequest) {
    return new RequestPostProcessor() {
        @Override
        public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
            return mockHttpServletRequest;
        }
    };
}

you would apply this post processor by adding it via with method

    this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).addFilter(springSecurityFilterChain).build();
    mockMvc.perform(post(URL).with(mockedRequest(request)));

here the request you pass to mockedRequest method, would be your original request holding the attributes you need

Master Slave
  • 27,771
  • 4
  • 57
  • 55
  • I tried this, but i dont' like the approach. My original problem is that the injected request is not the one, created for accessing the servlet. The solution you propose, instead of fixing the incorrect injection, alters the incoming request. Besides that, the currently injected request is a kind of singleton, created in the test context, so i don't want to alter it for all the test cases. And altering itself would mean copying all the initialization done in MockHttpServletRequestBuilder.buildRequest. – George Lezhava Jun 11 '15 at 06:36
0

Try this configuration:

@RunWith(SpringJUnit4ClassRunner.class)
public class MyTests extends AbstractContextControllerTests {

    @Test
    public void test() {
    }
}

Where AbstractContextControllerTests is this:

@WebAppConfiguration
@ContextConfiguration(classes = {DispatcherConfig.class}, loader = AnnotationConfigWebContextLoader.class)
public class AbstractContextControllerTests {

    @Autowired
    protected WebApplicationContext wac;

}

And DispatherConfig is this:

@Configuration
@EnableWebMvc
public class DispatcherConfig extends WebMvcConfigurerAdapter {
}
zygimantus
  • 3,649
  • 4
  • 39
  • 54