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...
The request scope creates a bean instance for a single HTTP request, while the session scope creates a bean instance for an HTTP Session. The application scope creates the bean instance for the lifecycle of a ServletContext, and the websocket scope creates it for a particular WebSocket session.
As singleton beans are injected only once per their lifetime you need to provide scoped beans as proxies which takes care of that. @RequestScope is a meta-annotation on @Scope that 1) sets the scope to "request" and 2) sets the proxyMode to ScopedProxyMode.
Prototype scope creates a new instance every time getBean method is invoked on the ApplicationContext. Whereas for request scope, only one instance is created for an HttpRequest.
When used as a type-level annotation in conjunction with @Component , @Scope indicates the name of a scope to use for instances of the annotated type. When used as a method-level annotation in conjunction with @Bean , @Scope indicates the name of a scope to use for the instance returned from the method.
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
@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()); } } }
@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 for all presented solutions:
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With