33

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
  • 13,006
  • 17
  • 55
  • 87

8 Answers8

61

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:

akokskis
  • 1,486
  • 15
  • 32
MariuszS
  • 30,646
  • 12
  • 114
  • 155
  • 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 Jan 30 '14 at 01:00
  • Solution with listener works just fine for me, tested, full code here: https://github.com/mariuszs/spring-test-web/blob/master/src/test/java/sample/MainControllerWithListenerTest.java – MariuszS Jan 30 '14 at 13:12
  • 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. – Dzmitry Lazerka Feb 13 '14 at 07:55
  • the xml needs map with two entries, I see two maps instead? – Kalpesh Soni Oct 11 '16 at 17:39
  • Based on the source, I am able to get it running using SpringJUnit4ClassRunner with spring 5. But it doesn't run for junit.jupter. – Abe Sep 01 '22 at 17:47
9

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!

Ido Cohn
  • 1,685
  • 3
  • 21
  • 28
  • 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. – jediz Feb 17 '16 at 17:14
9

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

    // ...
OrangeDog
  • 36,653
  • 12
  • 122
  • 207
8

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.

skaffman
  • 398,947
  • 96
  • 818
  • 769
  • 1
    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 Mar 10 '10 at 00:10
  • 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. – harschware Mar 10 '10 at 00:17
  • 2
    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. 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 Jan 30 '14 at 00:31
  • 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. – Dzmitry Lazerka Jan 30 '14 at 01:03
  • 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." – sumanr Jul 26 '17 at 09:30
2

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;
    }
}
stivlo
  • 83,644
  • 31
  • 142
  • 199
2

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

Jim Cox
  • 974
  • 7
  • 10
1

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

Christopher Yang
  • 3,769
  • 4
  • 30
  • 27
0

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.

user1050755
  • 11,218
  • 4
  • 45
  • 56