Announcement Announcement Module
Collapse
No announcement yet.
Integration tests with mocked session Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Integration tests with mocked session

    I need to setup integration tests testing a session scoped bean.
    I have looked up several forum conversation but was not able to find one fitting my requirements.

    Am actually trying to setup a testing environment as follows:

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations={"/spring-beans.xml"})
    @TransactionConfiguration(transactionManager="txMa nager", defaultRollback=true)
    @Transactional
    public class IntegrationTest extends AbstractTransactionalSpringContextTests {

    ...
    }

    By default running a test in this environment returns something like:
    No Scope registered for scope 'session'

    Understandable, as no request object has been created.

    Question:
    Where do I set modify the applicationContext to add the scoping and mocked servlet request ?

  • #2
    You need to register this scope in your tests as Spring doesn't assume your IoC container runs in a webcontext. You need to tell Spring you'd like session scope, you normally do this declaratively in your web.xml but in your test you do this programatically.

    The ConfigurableBeanFactory which the bean factory implementations implement has a method 'registerScope(String scopeName, Scope scope)'.

    So do something like factory.registerScope("session", new SessionScope());
    Do the same for request and any other non standard scope you require.

    You can also use Spring objects like MockHttpServletRequest, MockHttpServletResponse and MockHttpSession in your tests to create your http objects.

    I'd recommend creating something like an AbstractWebContextIntegrationTest (or a separate concrete class you have as an attribute) and putting this code in there so all your http dependent int tests can have this for free.

    It wouldn't be a bad thing if the Spring guys added a class like this to the test libraries...

    Comment


    • #3
      The main problem im facing is the fact that prior to any tests the applicationContext is read and created.
      During this process spring realizes that the scope=session is not possible due to the fact that Im not running my tests within a servlet environment.

      Thus from my point of view a solution would be to intercept or override the method loadContext or loadContexts in order to register the new scope into the applicationContext.

      My problem is that I have not yet found a propery testing class where I could override this method.

      Am I completely wrong ??

      Any help is appreciated

      Comment


      • #4
        You cast the applicationContext to a ConfigurableBeanFactory in the onSetup() method, then register the custom scope(s).

        If you're saying the container is already instantiated and the failure has already happened then that would be because you're declaring your spring config files using annoations - if that's the case then you probably shouldn't use annotations here, and instead overide getConfigLocations().

        Comment


        • #5
          I'd recommend creating a XML file that is specific to your integration test and register a custom scope:

          Code:
          <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
            <property name="scopes">
              <map>
                <entry key="session"><bean class="TestScope"/></entry>
              </map>
            </property>
          </bean>
          Unfortunately I don't think SessionScope will be acceptable because it requires a Session, or a reasonable approximation backing it. Also, "prototype" and "singleton" are not actual scopes, so you'd need to implement one. This class is the same as a prototype. If you wanted to be more similar to a singleton, in the get method you could store the bean in a HashMap using its name. If you wanted to get fancier you could build in support to your test class have the bean last the length of a single test, so the same bean would be returned during one test.

          Code:
          import org.springframework.beans.factory.config.Scope;
          import org.springframework.beans.factory.ObjectFactory;
          
          public class TestScope implements Scope {
              public String getConversationId() {
                  return null;
              }
          
              public Object get(String name, ObjectFactory objectFactory) {
                  return objectFactory.getObject();
              }
          
              public Object remove(String name) {
                  return null;
              }
          
              public void registerDestructionCallback(String name, Runnable callback) {
              }
          }
          Last edited by wpoitras; Mar 5th, 2008, 11:40 AM. Reason: Missing property name

          Comment


          • #6
            Your really a great help.

            There is one question left ..

            I have added code like:
            @Override
            protected void onSetUp() throws Exception {

            MockHttpServletRequest request = new MockHttpServletRequest();
            MockHttpSession session = new MockHttpSession();
            request.setSession(session);
            RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
            applicationContext.getBeanFactory().registerScope( "session", new SessionScope());
            }

            which should register the session scope and take care about the servlet request.
            It looks nice but the result is the same:

            "nested exception is java.lang.IllegalStateException: No Scope registered for scope 'session'"

            Is there any additional hint you could provide helping me to fix this issue ???

            Comment


            • #7
              I think if you register the SessionScope in XML using CustomScopeConfigurer instead of onSetUp it might work.

              Comment


              • #8
                Yes I think that would fix it. Otherwise you'd need to stop using the AbstractDependencyInjectionSpringContextTests test class and instantiate the container yourself. The problem is that using that class the container is created and accessed before you register the scope.

                So either do as wpoitras says or write a standard junit test and instantiate the container yourself, though you'd need to look up the object you want to test

                Code:
                ConfigurableApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-beans.xml"); 
                applicationContext.getBeanFactory().registerScope("session", new SessionScope());
                
                MockHttpServletRequest request = new MockHttpServletRequest();
                MockHttpSession session = new MockHttpSession();
                request.setSession(session);
                RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
                
                SomeObject someObject = (SomeObject) applicationContext.getBean("someObject");
                That will work as I've tested it...

                Comment


                • #9
                  Andy (and anyone else):

                  Were you able to get this to work? I'm trying to do the same thing (mocking RequestScope in this case for use with request scoped bean) and I simply cannot get it to function.

                  Comment


                  • #10
                    Leo, I had the same problem with session scope during unit testing webflow, and I found the following solution (hack?):
                    At the start of a test I add a flow execution listener that reacts to the 'sessionCreated' event. Here you have access to the request context and can put things in all available scopes.
                    Example:
                    Code:
                    public void testFoo() {
                       setFlowExecutionListener(
                          new FlowExecutionListenerAdapter() {
                             public void sessionCreated(RequestContext ctx, FlowSession session) {
                                ctx.getExternalContext().getSessionMap().put('someObjectName', new SomeObject());
                             }
                          }
                       );
                       // tests
                    }
                    Regards,
                    Marnix

                    Comment


                    • #11
                      Did somebody find complete soluton for that?

                      tomatrix,
                      I belive your solution works but I inherit my test clasess from
                      AbstractTransactionalSpringContextTests.

                      and the tweak is not acceptable for me.
                      Thank you,
                      /Paul.

                      Comment


                      • #12
                        A working solution

                        We can override the customizeBeanFactory method and add the session scope to the default bean factory. For a session scoped bean to work properly we need to have a http request exposed to the thread.

                        /**
                        * Customizes the default bean factory by adding session scope and creating a mock HTTP request
                        *
                        * @param beanFactory - Default bean factory
                        */
                        @Override
                        protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
                        beanFactory.registerScope("session", new SessionScope());
                        MockHttpServletRequest req = new MockHttpServletRequest();
                        ServletRequestAttributes attrbs = new ServletRequestAttributes(req);
                        RequestContextHolder.setRequestAttributes(attrbs);
                        }

                        Comment


                        • #13
                          Saw this thread and wanted to give my solution. It is an extension of the solution described above.

                          What is central in the solution is that the webapplication context is recreated. It is based on the statement which I found in the Spring documentation:

                          The scopes that are described in the following paragraphs are only available if you are using a web-aware Spring ApplicationContext implementation (such as XmlWebApplicationContext). If you try using these next scopes with regular Spring IoC containers such as the XmlBeanFactory or ClassPathXmlApplicationContext, you will get an IllegalStateException complaining about an unknown bean scope.

                          The scopes described are for example the request, and conversation scope. This all leads to the following steps to follow:
                          1. Use the XmlWebApplicationContext as an application context. This application context has the advantage that it has the request object available.
                          2. Set the request object by:
                            MockHttpServletRequest request = new MockHttpServletRequest();
                            MockHttpSession session = new MockHttpSession();
                            request.setSession(session);
                            RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
                          3. For this you need the spring-mock.jar for the extra mock objects

                          Comment


                          • #14
                            Spring + JUnit4 + Session Scope

                            So I spent today messing around with this same stuff and came up with the following solution...

                            Start with a basic Spring Test
                            Code:
                            @RunWith(SpringJUnit4ClassRunner.class)
                            @ContextConfiguration(locations=("test-spring-config.xml"))
                            @TestExecutionListeners({DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class})
                            Change Context Configuration
                            Code:
                            @ContextConfiguration(locations=("test-spring-config.xml"), loader=TestWebSessionContextLoader.class)
                            Add TestWebContextLoader.java (no session yet, but a good base to extend for request, etc) - this is basically a rip-off of the AbstractGenericContextLoader and GenericXmlContextLoader, in fact, this would all be easier if the AbstractGenericContextLoader didn't have loadContext declared as final. However, one important difference is that the context refresh is in customizeContext as it must be refreshed before registerScope is called for the bean factory.
                            Code:
                            import org.apache.commons.logging.Log;
                            import org.apache.commons.logging.LogFactory;
                            
                            import org.springframework.beans.factory.support.BeanDefinitionReader;
                            import org.springframework.beans.factory.support.DefaultListableBeanFactory;
                            import org.springframework.context.ConfigurableApplicationContext;
                            import org.springframework.context.annotation.AnnotationConfigUtils;
                            import org.springframework.context.support.GenericApplicationContext;
                            import org.springframework.web.context.support.GenericWebApplicationContext;
                            import org.springframework.util.StringUtils;
                            import org.springframework.test.context.support.AbstractContextLoader;
                            import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
                            
                            public class TestWebContextLoader extends AbstractContextLoader {
                            
                            	protected static final Log logger = LogFactory
                            			.getLog(TestWebContextLoader.class);
                            
                            	public final ConfigurableApplicationContext loadContext(
                            			final String... locations) throws Exception {
                            
                            		if (logger.isDebugEnabled()) {
                            			logger.debug("Loading ApplicationContext for locations ["
                            							+ StringUtils.arrayToCommaDelimitedString(locations)
                            							+ "].");
                            		}
                            
                            		GenericWebApplicationContext context = new GenericWebApplicationContext();
                            		customizeBeanFactory(context.getDefaultListableBeanFactory());
                            		createBeanDefinitionReader(context).loadBeanDefinitions(locations);
                            		AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
                            		customizeContext(context);
                            		
                            		context.registerShutdownHook();
                            		return context;
                            	}
                            
                            	protected void customizeBeanFactory(
                            			final DefaultListableBeanFactory beanFactory) {
                            		/* no-op */
                            	}
                            
                            	protected void customizeContext(final GenericWebApplicationContext context) {
                                                  /* refresh must be called when customizeContext is overriden */
                            		context.refresh();
                            	}
                            
                            	protected BeanDefinitionReader createBeanDefinitionReader(final GenericApplicationContext context) {
                            		return new XmlBeanDefinitionReader(context);
                            	}
                            
                            	@Override
                            	public String getResourceSuffix() {
                            		return "-context.xml";
                            	}
                            }
                            Add Session-version of the TestWebContextLoader: TestWebSessionContextLoader.java
                            Code:
                            import org.springframework.web.context.support.GenericWebApplicationContext;
                            
                            import org.springframework.web.context.request.RequestContextHolder;
                            import org.springframework.web.context.request.ServletRequestAttributes;
                            import org.springframework.web.context.request.SessionScope;
                            import org.springframework.mock.web.MockHttpServletRequest;
                            import org.springframework.mock.web.MockHttpSession;
                            import org.springframework.mock.web.MockServletContext;
                            
                            
                            public class TestWebSessionContextLoader extends TestWebContextLoader {
                            	
                            	@Override
                            	protected void customizeContext(final GenericWebApplicationContext context) {
                            		MockServletContext servlet = new MockServletContext();
                            		context.setServletContext(servlet);
                            		
                            		MockHttpServletRequest request = new MockHttpServletRequest();
                            		MockHttpSession session = new MockHttpSession();
                            		request.setSession(session);
                            		RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
                            	
                            		context.refresh();
                            		context.getBeanFactory().registerScope("session", new SessionScope());
                            	}
                            }
                            Tada! You now have an ApplicationContext with a mock session. If there's any issues, let me know, but I think it works ok.

                            Comment


                            • #15
                              The solution with a special test context in xml and a registered TestScope class worked like a charm for me! Thanks!

                              Comment

                              Working...
                              X