Announcement Announcement Module
Collapse
No announcement yet.
Testing scope=session Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Testing scope=session

    I have a session object that is injected into my controller (Spring MVC).

    I am having great difficulty writing an integration test for it.

    I can configure my WebApplicationContext just fine, but when I try and run my controller test I get the following error

    Code:
    org.springframework.beans.factory.BeanCreationException: Error creating bean with name '__shoppingBasket': Scope 'session' is not active; nested exception is java.lang.IllegalStateException: No thread-bound request: use RequestContextFilter
    Caused by: java.lang.IllegalStateException: No thread-bound request: use RequestContextFilter
    	at org.springframework.web.context.scope.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:60)
    	at org.springframework.web.context.scope.SessionScope.get(SessionScope.java:77)
    	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:274)
    	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:153)
    	at org.springframework.aop.target.AbstractPrototypeBasedTargetSource.newPrototypeInstance(AbstractPrototypeBasedTargetSource.java:58)
    	at org.springframework.aop.target.PrototypeTargetSource.getTarget(PrototypeTargetSource.java:30)
    	at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.getTarget(Cglib2AopProxy.java:673)
    	at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:624)
    	at com.au.odin.web.support.ShoppingBasketBean$$EnhancerByCGLIB$$1a85fb85.add(<generated>)
    	at com.au.odin.web.spring.ShoppingBasketController.handleShop(ShoppingBasketController.java:45)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    	at java.lang.reflect.Method.invoke(Method.java:585)
    	at org.springframework.web.servlet.mvc.multiaction.MultiActionController.invokeNamedMethod(MultiActionController.java:434)
    	at org.springframework.web.servlet.mvc.multiaction.MultiActionController.handleRequestInternal(MultiActionController.java:372)
    	at org.springframework.web.servlet.mvc.AbstractController.handleRequest(AbstractController.java:153)
    	at com.au.odin.web.spring.ShoppingBasketControllerTest$1.onRequest(ShoppingBasketControllerTest.java:43)
    	at com.au.commons.test.spring.ControllerTestsHelper.submitRequest(ControllerTestsHelper.java:66)
    	at com.au.commons.test.dbunit.spring.ControllerTests.submitRequest(ControllerTests.java:100)
    	at com.au.commons.test.dbunit.spring.ControllerTests.submitRequest(ControllerTests.java:77)
    	at com.au.commons.test.dbunit.spring.HibernateControllerTests.submitRequest(HibernateControllerTests.java:53)
    	at com.au.odin.web.spring.ShoppingBasketControllerTest.testAddItem(ShoppingBasketControllerTest.java:38)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    	at java.lang.reflect.Method.invoke(Method.java:585)
    	at junit.framework.TestCase.runTest(TestCase.java:154)
    	at junit.framework.TestCase.runBare(TestCase.java:127)
    	at org.springframework.test.ConditionalTestCase.runBare(ConditionalTestCase.java:69)
    	at junit.framework.TestResult$1.protect(TestResult.java:106)
    	at junit.framework.TestResult.runProtected(TestResult.java:124)
    	at junit.framework.TestResult.run(TestResult.java:109)
    	at junit.framework.TestCase.run(TestCase.java:118)
    	at junit.framework.TestSuite.runTest(TestSuite.java:208)
    	at junit.framework.TestSuite.run(TestSuite.java:203)
    	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:478)
    	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:344)
    	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
    This is how I instantiate my WebApplicationContext.
    Code:
            String path[] = {"applicationContext.xml"            
                };
            
            applicationContext = new XmlWebApplicationContext();
            applicationContext.setConfigLocations(path);
            applicationContext.setServletContext(new MockServletContext(""));
            
            
            applicationContext.refresh();
    I have discovered the MockFilterConfig but am not sure about how to make the WebApplicationContext aware of it.

  • #2
    For request- or session-scoped beans you do not only need a web-aware ApplicationContext, but also you need to imitate the functionality of Spring's RequestContextListener, i.e. providing a ServletRequest.

    I don't know how easy this is and what will get a best practice in future. I for myself replace scoped beans for my testcases with pure singletons and switch the config between tests and webapp.

    Jörg

    Comment


    • #3
      Thanks for the reply Jörg but I would like to be able to implement this functionality in my controller.

      We use continuous integration at my current client and it would be nice to not "break the build". It is also pretty tedious to keep changing your config file.

      Comment


      • #4
        Originally posted by CodeKing
        It is also pretty tedious to keep changing your config file.
        Of course I don't do this from hand. There are different techniques known to change the config in different environments, e.g. with different property files.

        I have separated the web-specific stuff into its own config file. So in the test environment I can provide a different config file on the classpath than in the web environment.

        Jörg

        Comment


        • #5
          Originally posted by Jörg Heinicke View Post

          I don't know how easy this is and what will get a best practice in future. I for myself replace scoped beans for my testcases with pure singletons and switch the config between tests and webapp.
          I have been using Spring 2.0 in some projects and have started using the session scope more extensively. I have a set of integration test cases that initialize the spring context and am performing integration tests on a bean that is Configurable with injected Session scoped beans. I saw this post as really the only thing I found to assist with performing integration tests with session scoped beans, and found an alternative way of enabling these to function properly.

          In my base integration test case, when I am initializing the context files I do some extra work to create a WebApplicationContext as well, so I am already overriding the loadContextLocations method of AbstractTransactionalDataSourceSpringContextTests. In that method after I initialize the context that contains my session scoped beans I simply add the below method:

          Code:
          applicationContext.getBeanFactory().registerScope("session", new SessionScope());
          You will need to be cognizant if you have hierarchical contexts, as this needs to be set on all contexts that you create that contain session scoped beans. In the onSetupBeforeTransaction method I have:

          Code:
          request = new MockHttpServletRequest();
          RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
          In the corresponding tear down method, I'm also setting the requestAttributes to null because I believe they are in a ThreadLocal object. With those couple steps I am now able to use session scoped beans in my integration tests, without having to swap out alternate configurations for testing; I find I already have to do that enough that it's becoming difficult to consider these integration tests

          Comment


          • #6
            There should be Some TestCase subclass that do the same job as the RequestContextListener do for initialization.
            I suggest doing something like this (I did it for my project):
            Code:
            public class WebAwareTestCase extends AbstractTransactionalDataSourceSpringContextTests {
            
              ...
            
              public final void runBare() throws Throwable {
                this.initializeContext();
                this.setUp();
                try {
                  this.runTest();
                } finally {
                  this.tearDown();
                  this.finalizeContext();
                }
              }
            
              private void initializeContext() {
                this.setHttpServletRequest(new MockHttpServletRequest());
                ServletRequestAttributes attributes = new ServletRequestAttributes(this
                    .getHttpServletRequest());
                this.getHttpServletRequest().setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE,
                    attributes);
                LocaleContextHolder.setLocale(this.getHttpServletRequest().getLocale());
                RequestContextHolder.setRequestAttributes(attributes);
                if (logger.isDebugEnabled()) {
                  logger.debug("Bound request context to thread: "
                      + this.getHttpServletRequest());
                }
              }
            
              private void finalizeContext() {
                ServletRequestAttributes attributes = (ServletRequestAttributes) this
                    .getHttpServletRequest().getAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE);
                ServletRequestAttributes threadAttributes = (ServletRequestAttributes) RequestContextHolder
                    .getRequestAttributes();
                if (threadAttributes != null) {
                  // We're assumably within the original request thread...
                  if (attributes == null) {
                    attributes = threadAttributes;
                  }
                  RequestContextHolder.setRequestAttributes(null);
                  LocaleContextHolder.setLocale(null);
                }
                if (attributes != null) {
                  attributes.requestCompleted();
                  if (logger.isDebugEnabled()) {
                    logger.debug("Cleared thread-bound request context: "
                        + this.getHttpServletRequest());
                  }
                }
              }
            
              protected ConfigurableApplicationContext createApplicationContext(
                  String[] locations) {
                GenericWebApplicationContext context = new GenericWebApplicationContext();
                context.setServletContext(new MockServletContext());
                customizeBeanFactory(context.getDefaultListableBeanFactory());
                new XmlBeanDefinitionReader(context).loadBeanDefinitions(locations);
                context.refresh();
                return context;
              }
            }
            What do you think about adding it as a requirement for new versions?

            Leandro

            Comment


            • #7
              The cases where you need session-scoped beans in your test should be rather rare. So in my opinion it does not make much sense to apply this to all tests.

              Joerg

              Comment


              • #8
                Ok, but if you use session or request scoped beans in your project and load the same files for tests, the application context must have the session or request scopes registered... If not, an unregistered scope exception will be thrown...
                I´m right?

                Leandro

                Comment


                • #9
                  My point is that you usually do unit tests. And you don't need to test the scoping again. I use these scopes in my application too, but I have set up my tests to inject plain objects instead of scoped proxies.

                  In the cases you really want to test the scoping or do integration tests you need indeed to register the scopes.

                  Does this answer your question?

                  Joerg

                  Comment


                  • #10
                    Originally posted by Jörg Heinicke View Post
                    My point is that you usually do unit tests. And you don't need to test the scoping again.
                    Sure, I suppose it´s tested by Spring.

                    Originally posted by Jörg Heinicke View Post
                    I use these scopes in my application too, but I have set up my tests to inject plain objects instead of scoped proxies.
                    That is what I don´t want / have to do. To define different configuration for application and tests.

                    Originally posted by Jörg Heinicke View Post
                    In the cases you really want to test the scoping or do integration tests you need indeed to register the scopes.
                    This is my case, I´m doing integration tests...

                    I think adding to the framework a TestCase subclass that helps doing integration tests using scoped beans is useful.
                    What do you think?

                    Regards
                    Leandro

                    Comment


                    • #11
                      Well, i have some beans taht use the request scope. To set up my test enviroment i do an abstract class that has all the mess about emulating the web enviroment for my junit test case.

                      Here is the code:

                      Code:
                      package com.inxside.sap.test;
                      
                      import javax.servlet.ServletRequestEvent;
                      
                      import junit.framework.TestCase;
                      
                      import org.springframework.context.ConfigurableApplicationContext;
                      import org.springframework.mock.web.MockHttpServletRequest;
                      import org.springframework.mock.web.MockServletContext;
                      import org.springframework.web.context.request.RequestContextListener;
                      import org.springframework.web.context.support.XmlWebApplicationContext;
                      import org.springframework.web.util.Log4jWebConfigurer;
                      
                      public abstract class AbstractSpringTest extends TestCase {
                      	protected ConfigurableApplicationContext ctx;
                      
                      	private MockServletContext sc;
                      
                      	public AbstractSpringTest() {
                      		XmlWebApplicationContext root = new XmlWebApplicationContext();
                      
                      		sc = new MockServletContext("");
                      		sc.addInitParameter(Log4jWebConfigurer.CONFIG_LOCATION_PARAM,
                      				"file:sap/WEB-INF/log4j.properties");
                      		
                      		root.setServletContext(sc);
                      		root.setConfigLocations(new String[] {
                      				"file:sap/WEB-INF/applicationContext.xml",
                      				"file:sap/WEB-INF/spring-dao.xml",
                      				"file:sap/WEB-INF/spring-action.xml",
                      				"file:sap/WEB-INF/spring-vo.xml",
                      				"file:sap/WEB-INF/spring-bo.xml" });
                      		MockHttpServletRequest mhsr = new MockHttpServletRequest();
                      		RequestContextListener rcl =  new RequestContextListener();
                      		ServletRequestEvent sre = new ServletRequestEvent(sc, new MockHttpServletRequest());
                      		rcl.requestDestroyed(sre);
                      		rcl.requestInitialized(sre);
                      		root.refresh();	
                      		
                      		
                      		Log4jWebConfigurer.initLogging(sc);
                      		ctx =root;
                      	}
                      }
                      And works very well!!. All my junit tets case extends from this class, and all of them have access to the simulated web-container.
                      By the way, i have to change my appliccationContext.xml and spring-dao.xml in the following lines:
                      appliccationContext.xml
                      Code:
                      ...
                      <bean id="propertyConfigurer"
                      		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
                      		<property name="locations">
                      			<list>
                      				<value>file:sap/WEB-INF/jdbc.properties</value>
                      <!--				<value>WEB-INF/jdbc.properties</value>-->
                      			</list>
                      		</property>
                      	</bean>
                      ...
                      spring-dao.xml
                      Code:
                      <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
                      <!--		<property name="configLocation" value="WEB-INF/sql-map-config.xml"/>-->
                      		<property name="configLocation" value="file:sap/WEB-INF/sql-map-config.xml"/>
                      		<property name="dataSource" ref="dataSource"/>
                      	</bean>
                      As you can see i comment some lines and that's all, i have my configuration files ready to be deployed in a server or ready for test unit.

                      i hope this help

                      Comment


                      • #12
                        Originally posted by leaqui View Post
                        I think adding to the framework a TestCase subclass that helps doing integration tests using scoped beans is useful.
                        What do you think?
                        You can always file an enhancement request to Spring's Jira.

                        Joerg

                        Comment


                        • #13
                          Originally posted by leaqui View Post
                          I think adding to the framework a TestCase subclass that helps doing integration tests using scoped beans is useful.
                          What do you think?
                          I think is a good idea. Because even it is possible to set up Test Cases to test scoped beans (session beans or request beans), it will be good to have a class that it has the necessary code to test scoped beans.

                          Comment

                          Working...
                          X