request scoped beans in spring testing
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;
}
}
harschware
I am a semantic web / Java developer. Previously I have been an Oracle DBA and Perl programmer.
Updated on March 09, 2021Comments
-
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 about 14 yearsAh 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 about 14 yearsOh, 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 over 10 yearsThank 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 over 10 yearsWell, it fails on the first line
testContext.getApplicationContext()
with error messageNo 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 over 10 yearsAlso, 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 over 10 yearsSolution with listener works just fine for me, tested, full code here: github.com/mariuszs/spring-test-web/blob/master/src/test/java/…
-
Dzmitry Lazerka about 10 yearsBecause 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 about 8 yearsThe "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 over 7 yearsthe xml needs map with two entries, I see two maps instead?
-
sumanr almost 7 yearsThis 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 about 2 yearsin Junit5, this helped by putting in the parent BastTest call and using
@BeforeEach