3

In Spring 3.1, I could autowire a Jax-RS resource that had `@Scope("request") into my unit tests provided I included the following BeanFactoryPostProcessor:

@Component
public class MockRequestBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

public void postProcessBeanFactory(
        ConfigurableListableBeanFactory beanFactory) throws BeansException {

    beanFactory.registerScope("request", new RequestScope());
    MockHttpServletRequest request = new MockHttpServletRequest();
    ServletRequestAttributes attributes = new ServletRequestAttributes(request);
    RequestContextHolder.setRequestAttributes(attributes);
}

}

With Spring 3.2, the first test method that runs works, but all subsequent test methods get

java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.

How can I get my tests working again?

Aleksandr Dubinsky
  • 22,436
  • 15
  • 82
  • 99

2 Answers2

7

The BeanFactoryPostProcessor is flawed, it runs only once and so only a single thread will have a (not reusable) MockHttpServletRequest.

Moving the code which creates the request and stores it in the RequestContextHolder should be moved to a @Before annotated method and in an @After annotated method you should cleanup the RequestContextHolder.

@Before
public void init() {
    MockHttpServletRequest request = new MockHttpServletRequest();
    ServletRequestAttributes attributes = new ServletRequestAttributes(request);
    RequestContextHolder.setRequestAttributes(attributes);
}

@After
public void cleanUp() {
    RequestContextHolder.resetRequestAttributes();
}

You still need the BeanFactoryPostProcessor to register the RequestScope.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
  • This doesn't work, because Spring autowires my test class before @Before methods are called. – Aleksandr Dubinsky Dec 03 '13 at 10:41
  • You could do a lookup instead. Another thing you can add/try is adding the `@WebAppConfiguration` annotation to your test-class (I assume that you are using Springs test support for loading and autowiring). Which should remove the need for the `BeanFactoryPostProcessor` and both the `@Before`/`@After` methods. – M. Deinum Dec 03 '13 at 10:49
0

Spring 3.2 introduced the ServletTestExecutionListener, which rudely injects itself into your old tests.

Its javadoc:

TestExecutionListener which provides mock Servlet API support to WebApplicationContexts loaded by the Spring TestContext Framework.

Specifically, ServletTestExecutionListener sets up thread-local state via Spring Web's RequestContextHolder during test instance preparation and before each test method and creates a MockHttpServletRequest, MockHttpServletResponse, and ServletWebRequest based on the MockServletContext present in the WebApplicationContext. This listener also ensures that the MockHttpServletResponse and ServletWebRequest can be injected into the test instance, and once the test is complete this listener cleans up thread-local state.

Note that ServletTestExecutionListener is enabled by default but takes no action if the ApplicationContext loaded for the current test is not a WebApplicationContext.

The problem is that the last paragraph is a lie. True, this class doesn't start helping you unless you make configuration changes, but it will gleefully reset the request after each test, no matter what.

The listener can be disabled by adding @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class }) to the test class. (The actual set of listeners you may want to edit as needed.)

Alternatively, you can set @WebAppConfiguration and remove the MockRequestBeanFactoryPostProcessor as well other legacy work-arounds like the MockServletContextAwareProcessor.

Community
  • 1
  • 1
Aleksandr Dubinsky
  • 22,436
  • 15
  • 82
  • 99
  • I would go with the default and ditch the work-arounds (less code to maintain :) ). Good catch on the resetting, you might want to register a [JIRA](http://jira.springsource.org) bug/enhanchement for that. – M. Deinum Dec 03 '13 at 12:20
  • @M.Deinum Thing is, as per http://stackoverflow.com/questions/8878190/running-unit-tests-on-the-server-jax-rs/9041446#9041446, I do some fancy things with ServletContexts and unit tests. I need to set the resource path, but `@WebAppConfiguration.value` gets fixed at compile time. So I think I'll keep using my custom ServletContextAwareProcessor. In any case, when I run junit manually (programmatically via JUnitCore), the built-in SCAP doesn't work (`@WAC` bug?), so I have no choice. But seems I can use `@WAC` and my custom SCAP together, while skipping the `RequestBeanFactoryPostProcessor`. – Aleksandr Dubinsky Dec 03 '13 at 17:53