request scoped beans in spring testing

By | December 3, 2017
Questions:

I would like to make use of request scoped beans in my app. I use JUnit4 for testing. If I try to create one in a test like this:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:spring/TestScopedBeans-context.xml" })
public class TestScopedBeans {
    protected final static Logger logger = Logger
            .getLogger(TestScopedBeans.class);

    @Resource
    private Object tObj;

    @Test
    public void testBean() {
        logger.debug(tObj);
    }

    @Test
    public void testBean2() {
        logger.debug(tObj);
    }

With the following bean definition:

 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  <bean class="java.lang.Object" id="tObj" scope="request" />
 </beans>           

And I get:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'gov.nasa.arc.cx.sor.query.TestScopedBeans': Injection of resource fields failed; nested exception is java.lang.IllegalStateException: No Scope registered for scope 'request'
<...SNIP...>
Caused by: java.lang.IllegalStateException: No Scope registered for scope 'request'

So I found this blog that seemed helpful:
http://www.javathinking.com/2009/06/no-scope-registered-for-scope-request_5.html

But I noticed he uses AbstractDependencyInjectionSpringContextTests which seems to be deprecated in Spring 3.0.
I use Spring 2.5 at this time, but thought it shouldn’t be too hard to switch this method to use AbstractJUnit4SpringContextTests
as the docs suggest (ok the docs link to the 3.8 version but I’m using 4.4). So I change the
test to extend AbstractJUnit4SpringContextTests… same message. Same problem. And now the prepareTestInstance() method I want
to override is not defined. OK, maybe I’ll put those registerScope calls somewhere else… So I read more about TestExecutionListeners and think that would be better since I don’t want to have to inherit the spring package structure. So
I changed my Test to:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:spring/TestScopedBeans-context.xml" })
@TestExecutionListeners({})
public class TestScopedBeans {

expecting I would have to create a custom listener but I when I ran it. It works! Great, but why? I don’t see where any of the stock listeners
are registering request scope or session scope, and why would they? there’s nothing to say I want that yet, this might not be a Test for Spring MVC code…

Answers:

The test passes because it isn’t doing anything 🙂

When you omit the @TestExecutionListeners annotation, Spring registers 3 default listeners, including one called DependencyInjectionTestExecutionListener. This is the listener responsible for scanning your test class looking for things to inject, including @Resource annotations. This listener tried to inject tObj, and fails, because of the undefined scope.

When you declare @TestExecutionListeners({}), you suppress the registration of the DependencyInjectionTestExecutionListener, and so the test never gets tObj injected at all, and because your test is not checking for the existence of tObj, it passes.

Modify your test so that it does this, and it will fail:

@Test
public void testBean() {
    assertNotNull("tObj is null", tObj);
}

So with your empty @TestExecutionListeners, the test passes because nothing happens.

Now, on to your original problem. If you want to try registering the request scope with your test context, then have a look at the source code for WebApplicationContextUtils.registerWebApplicationScopes(), you’ll find the line:

beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());

You could try that, and see how you go, but there might be odd side-effects, because you’re not really meant to do this in a test.

Instead, I would recommend rephrasing your test so that you don’t need request scoped beans. This shouldn’t be difficult, the lifecycle of the @Test shouldn’t be any longer than the lifecycle of a request-scoped bean, if you write self-contained tests. Remember, there’s no need to test the scoping mechanism, it’s part of Spring and you can assume it works.

Questions:
Answers:

Solution for Spring 3.2 or newer

Spring starting with version 3.2 provides support for session/request scoped beans for integration testing.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfig.class)
@WebAppConfiguration
public class SampleTest {

    @Autowired WebApplicationContext wac;

    @Autowired MockHttpServletRequest request;

    @Autowired MockHttpSession session;    

    @Autowired MySessionBean mySessionBean;

    @Autowired MyRequestBean myRequestBean;

    @Test
    public void requestScope() throws Exception {
        assertThat(myRequestBean)
           .isSameAs(request.getAttribute("myRequestBean"));
        assertThat(myRequestBean)
           .isSameAs(wac.getBean("myRequestBean", MyRequestBean.class));
    }

    @Test
    public void sessionScope() throws Exception {
        assertThat(mySessionBean)
           .isSameAs(session.getAttribute("mySessionBean"));
        assertThat(mySessionBean)
           .isSameAs(wac.getBean("mySessionBean", MySessionBean.class));
    }
}

Read more: Request and Session Scoped Beans


Solution for Spring before 3.2 with listener

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfig.class)
@TestExecutionListeners({WebContextTestExecutionListener.class,
        DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class})
public class SampleTest {
    ...
}

WebContextTestExecutionListener.java

public  class WebContextTestExecutionListener extends AbstractTestExecutionListener {
    @Override
    public void prepareTestInstance(TestContext testContext) {
        if (testContext.getApplicationContext() instanceof GenericApplicationContext) {
            GenericApplicationContext context = (GenericApplicationContext) testContext.getApplicationContext();
            ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
            beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST,
                    new SimpleThreadScope());
            beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION,
                    new SimpleThreadScope());
        }
    }
}

Solution for Spring before 3.2 with custom scopes

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfig.class, locations = "test-config.xml")
public class SampleTest {

...

}

TestConfig.java

@Configuration
@ComponentScan(...)
public class TestConfig {

    @Bean
    public CustomScopeConfigurer customScopeConfigurer(){
        CustomScopeConfigurer scopeConfigurer = new CustomScopeConfigurer();

        HashMap<String, Object> scopes = new HashMap<String, Object>();
        scopes.put(WebApplicationContext.SCOPE_REQUEST,
                new SimpleThreadScope());
        scopes.put(WebApplicationContext.SCOPE_SESSION,
                new SimpleThreadScope());
        scopeConfigurer.setScopes(scopes);

        return scopeConfigurer

}

or with xml configuration

test-config.xml

<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
    <property name="scopes">
        <map>
            <entry key="request">
                <bean class="org.springframework.context.support.SimpleThreadScope"/>
            </entry>
        </map>
        <map>
            <entry key="session">
                <bean class="org.springframework.context.support.SimpleThreadScope"/>
            </entry>
        </map>
    </property>
</bean>

Source code

Source code for all presented solutions:

Questions:
Answers:

I’ve tried several solutions, including @Marius’s solution with the “WebContextTestExecutionListener”, but it didn’t work for me, as this code loaded the application context before creating the request scope.

The answer that helped me in the end is not a new one, but it’s good:
http://tarunsapra.wordpress.com/2011/06/28/junit-spring-session-and-request-scope-beans/

I simply added the following snippet to my (test) application context:

<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
    <property name="scopes">
        <map>
            <entry key="request">
                <bean class="org.springframework.context.support.SimpleThreadScope"/>
            </entry>
        </map>
    </property>
</bean>

Good luck!

Questions:
Answers:

This is still an open issue:

https://jira.springsource.org/browse/SPR-4588

I was able to get this to work (mostly) by defining a custom context loader as outlined in

http://forum.springsource.org/showthread.php?p=286280

Questions:
Answers:

Test Request-Scoped Beans with Spring explains very well how to register and create a custom scope with Spring.

In a nutshell, as Ido Cohn explained, it’s enough to add the following to the text context configuration:

<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
    <property name="scopes">
        <map>
            <entry key="request">
                <bean class="org.springframework.context.support.SimpleThreadScope"/>
            </entry>
        </map>
    </property>
</bean>

Instead of using the predefined SimpleThreadScope, based on ThreadLocal, it’s also easy to implement a Custom one, as explained in the article.

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;

public class CustomScope implements Scope {

    private final Map<String , Object> beanMap = new HashMap<String , Object>();

    public Object get(String name, ObjectFactory<?> factory) {
        Object bean = beanMap.get(name);
        if (null == bean) {
            bean = factory.getObject();
            beanMap.put(name, bean);
        }
        return bean;
    }

    public String getConversationId() {
        // not needed
        return null;
    }

    public void registerDestructionCallback(String arg0, Runnable arg1) {
        // not needed
    }

    public Object remove(String obj) {
        return beanMap.remove(obj);
    }

    public Object resolveContextualObject(String arg0) {
        // not needed
        return null;
    }
}

Questions:
Answers:

MariuszS’ solution works, except I couldn’t get the transaction committed properly.

It seems the newly released 3.2 has finally made testing request/session scoped beans first class citizens. Here’s a couple of blogs for more details.

Rossen Stoyanchev’s Spring Framework 3.2 RC1: Spring MVC Test Framework

Sam Brannen’s Spring Framework 3.2 RC1: New Testing Features

Questions:
Answers:

NOT reading the docs sometimes drives one crazy. Almost.

If you are using shorter-lived beans (request scope for example), you most likely also need to change your lazy init default! Otherwise the WebAppContext will fail to load and tell you something about missing request scope, which is of course missing, because the context is still loading!

http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/beans.html#beans-factory-lazy-init

The Spring guys should definitely put that hint into their exception message…

If you don’t want to change the default, there is also the annotation way: put “@Lazy(true)” after @Component etc. to make singletons initialize lazy and avoid instantiating request-scoped beans too early.

Questions:
Answers:

A solution, tested with Spring 4, for when you require request-scoped beans but aren’t making any requests via MockMVC, etc.

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(/* ... */)
public class Tests {

    @Autowired
    private GenericApplicationContext context;

    @Before
    public void defineRequestScope() {
        context.getBeanFactory().registerScope(
            WebApplicationContext.SCOPE_REQUEST, new RequestScope());
        RequestContextHolder.setRequestAttributes(
            new ServletRequestAttributes(new MockHttpServletRequest()));
    }

    // ...

Leave a Reply

Your email address will not be published. Required fields are marked *