request scoped beans in spring testing

54,060

Solution 1

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.

Solution 2

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:

Solution 3

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!

Solution 4

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

    // ...

Solution 5

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;
    }
}
Share:
54,060
harschware
Author by

harschware

I am a semantic web / Java developer. Previously I have been an Oracle DBA and Perl programmer.

Updated on March 09, 2021

Comments

  • harschware
    harschware about 3 years

    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...

  • harschware
    harschware about 14 years
    Ah yes, thank you. I wasn't concerned with the test passing because I just wanted the bean created and when I wrote the test I didn't think I'd be turning off injection at some point:-). As for rephrasing the test... no. The whole point is to see how I can get request scoped beans to work in JUnit or the web app.
  • harschware
    harschware about 14 years
    Oh, and, when I said works I meant 'runs'. Again I was just looking for the Exception to go away, thinking that would mean I now have a request scoped bean.
  • Dzmitry Lazerka
    Dzmitry Lazerka over 10 years
    Thank you, but how would you "rephrase" your test to not need request scoped beans? Suppose I'm testing FooController that has @Autowired Provider<FooDao> fooDao. I'm not mocking fooDao, because this is integration test, not unit (unit-tests don't need Spring context at all), I really need real FooDao. How do you inject request-scoped fooDao?
  • Dzmitry Lazerka
    Dzmitry Lazerka over 10 years
    Well, it fails on the first line testContext.getApplicationContext() with error message No Scope registered for scope 'request', because it reads context from XML and @Configuration, and some beans are defined there with "request" scope. For example I have: @Configuration class MyConf { @Bean @Scope("request") provideFoo() {return new Foo()}}
  • Dzmitry Lazerka
    Dzmitry Lazerka over 10 years
    Also, to obtain a beanFactory, you need some Context. And it fails while reading that context: No Scope registered for scope 'request'. See also my comment to MariuszS answer.
  • MariuszS
    MariuszS over 10 years
    Solution with listener works just fine for me, tested, full code here: github.com/mariuszs/spring-test-web/blob/master/src/test/jav‌​a/…
  • Dzmitry Lazerka
    Dzmitry Lazerka about 10 years
    Because your @Configuration have no beans defined. Try adding a method in @Configuration, for example @Bean @Scope("request") @Autowired public String provideFoo(SomeBean dependency) {return dependency.toString()}. It fails because SomeBean is not created yet.
  • jediz
    jediz about 8 years
    The "problem" with @WebAppConfiguration seems to be it won't register real request scope for you. In case you need one for say a scoped proxy, register the scope manually using the answer above.
  • Kalpesh Soni
    Kalpesh Soni over 7 years
    the xml needs map with two entries, I see two maps instead?
  • sumanr
    sumanr almost 7 years
    This statement made my day - I was all along trying to make junit understand request scope. After reading this i just configured my test bean to be of prototype scope - and bingo all errors gone. I broke my head for over a day over this - "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."
  • Ashish Thukral
    Ashish Thukral about 2 years
    in Junit5, this helped by putting in the parent BastTest call and using @BeforeEach