Announcement Announcement Module
Collapse
No announcement yet.
Using MockServletContext & ContextLoader with Spring TestContextFramework Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Using MockServletContext & ContextLoader with Spring TestContextFramework

    Hi all,

    Imagine the following standard ant based web app layout.

    Code:
    -- src
    -- tests
    -- webapp
      -- WEB-INF
        -- a.xml
        -- b.xml
        -- etc
    I'm migrating tests from the old inheritance based spring test api to the new TestContext framework.

    The old tests extend TestCase and do something of this nature.

    Code:
    ServletContext servletContext = new MockServletContext("/webapp", new FileSystemResourceLoader());
    ConfigurableWebApplicationContext springContext = new XmlWebApplicationContext();
    springContext.setServletContext(servletContext);
    springContext.setConfigLocations(new String[] {"/WEB-INF/a.xml", "/WEB-INF/b.xml"});
    springContext.refresh();
    The reason the above is necessary is because there is code in the source directory which loads resources using paths like '/WEB-INF/blah.xml'. So the tests must also be relative to '/webapp' so that when the code runs it can find those resource paths.

    The new TestContextFramework obviously recommends something like below.

    Code:
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations={"/WEB-INF/a.xml", "/WEB-INF/b.xml"})
    public class MyTest {
    }
    I've also noticed that @ContextConfiguration has three arguments - inheritLocations, loader and locations.

    So my question is how can I use the TestContextFramework but also provide a MockServletContext with a base directory of /webapp so that all context file locations are relative to /webapp and can be specified like '/WEB-INF/my-context.xml'? Or phrased another way how can I make the test load the context relative to /webapp.

    I've noticed that there is a loader argument to @ContextConfiguration. I've also seen certain posts by Sam Brannen suggesting that a custom ContextLoader can be implemented. Is this a use case for a custom ContextLoader? If so is it possible to provide an example in relation to this post? If not then how else can this be achieved cleanly?

    Essentially when my root level test class loads the context for all other tests I want it to do so relative to /webapp. This has to work both with ant on command line and within eclipse.

    Many thanks.
    Last edited by Narada; Oct 5th, 2008, 09:17 AM.

  • #2
    Maybe the following is one potential solution?

    Integration test using custom context loader

    Here I specify the use of a custom class loader using the spring test context framework.

    Code:
    import org.junit.runner.RunWith;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(inheritLocations = true, loader = MockServletContextWebContextLoader.class, locations = {
            "/WEB-INF/b.xml", "/WEB-INF/b.xml" })
    public class MySpringIntegrationTestWithCustomContextLoader {
    }
    Custom context loader

    Here is the custom class loader itself. This is broadly based upon AbstractGenericContextLoader which Spring provides already. However there are two main differences.
    1. It incorporates a MockServletContext to establish the web application root as being '/webapp' passing in a resource base path and a file system resource loader to make your resource paths interpreted as relative file system locations as mentioned on MockServletContext.
    2. It uses an xml web application context but since that is not type compatible with the existing spring test api it uses a replacement type that Spring conveniently provides which implements ConfigurableWebApplicationContext - StaticWebApplicationContext.

    Code:
    import javax.servlet.ServletContext;
    
    import org.springframework.beans.factory.support.BeanDefinitionReader;
    import org.springframework.beans.factory.support.DefaultListableBeanFactory;
    import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
    import org.springframework.context.ConfigurableApplicationContext;
    import org.springframework.context.annotation.AnnotationConfigUtils;
    import org.springframework.core.io.FileSystemResourceLoader;
    import org.springframework.mock.web.MockServletContext;
    import org.springframework.test.context.support.AbstractContextLoader;
    import org.springframework.web.context.support.StaticWebApplicationContext;
    
    public class MockServletContextWebContextLoader extends AbstractContextLoader {
    
        private static final String SERVLET_CONTEXT_WEB_ROOT = "/webapp";
    
        @Override
        public final ConfigurableApplicationContext loadContext(String... locations) throws Exception {
    
            /*
             * Use a type which implements ConfigurableWebApplicationContext!
             */
            StaticWebApplicationContext context = new StaticWebApplicationContext();
            prepareContext(context);
            customizeBeanFactory(context.getDefaultListableBeanFactory());
            createBeanDefinitionReader(context).loadBeanDefinitions(locations);
            AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
            customizeContext(context);
            context.refresh();
            context.registerShutdownHook();
            return context;
        }
    
        protected void prepareContext(StaticWebApplicationContext context) {
    
            /*
             * Incorporate mock servlet context!
             */
            ServletContext servletContext = new MockServletContext(SERVLET_CONTEXT_WEB_ROOT,
                    new FileSystemResourceLoader());
            servletContext.setAttribute(StaticWebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
                    context);
            context.setServletContext(servletContext);
    
        }
    
        protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
        }
    
        protected BeanDefinitionReader createBeanDefinitionReader(StaticWebApplicationContext context) {
            return new XmlBeanDefinitionReader(context);
        }
    
        protected void customizeContext(StaticWebApplicationContext context) {
        }
    
        @Override
        protected String getResourceSuffix() {
            return "-context.xml";
        }
    
    }
    Can anyone confirm if I'm going along the right lines?

    Calling Sam Brannen :-)

    P.S. If you are reading this Sam the new TestContext framework rocks! :-)
    Last edited by Narada; Oct 25th, 2008, 07:18 AM.

    Comment


    • #3
      I have one addition question.

      MockServletContext states:

      For setting up a full WebApplicationContext in a test environment, you can use XmlWebApplicationContext (or GenericWebApplicationContext), passing in an appropriate MockServletContext instance. You might want to configure your MockServletContext with a FileSystemResourceLoader in that case, to make your resource paths interpreted as relative file system locations.
      Does this work if I use StaticWebApplicationContext instead of XmlWebApplicationContext? I am forced to use the former as the latter is not type compatible with the custom loader class given above.

      Comment


      • #4
        Hi all,

        I just wanted to confirm that I've tried that solution I posted above and it doesn't work. It appears that I'm still not within the resource base path of '/webapp' - I'm actually still in the root directory of the application so having to specify file:webapp/WEB-INF/blah.xml. Any ideas for solutions?

        Any help would be much appreciated.

        Thanks.
        Last edited by Narada; Oct 6th, 2008, 10:06 AM.

        Comment


        • #5
          This simple alternative way of doing it works.

          Code:
          import org.junit.BeforeClass;
          import org.junit.Test;
          import org.springframework.core.io.FileSystemResourceLoader;
          import org.springframework.mock.web.MockServletContext;
          import org.springframework.web.context.support.XmlWebApplicationContext;
          
          public class Junit4SpringIntegrationTest {
          
          	private static XmlWebApplicationContext context;
          
          	@BeforeClass
          	public static void setUpBeforeClass() throws Exception {
          
          		String[] contexts = new String[] { "/WEB-INF/applicationContext.xml" };
          		context = new XmlWebApplicationContext();
          		context.setConfigLocations(contexts);
          		context.setServletContext(new MockServletContext("/webapp",new FileSystemResourceLoader()));
          		context.refresh();
          
          	}
          
          	@Test
          	public void test() {
          
          	}
          
          }
          So what's the difference between this and the way the first solution above works? I really don't want to fall back to loading context manually like this without using the testcontext framework.

          Would much appreciate any help.
          Last edited by Narada; Oct 25th, 2008, 07:17 AM.

          Comment


          • #6
            FYI: you may be interested in the following JIRA issue entitled "Add support for loading a WebApplicationContext with the Spring TestContext Framework":

            http://jira.springframework.org/browse/SPR-5243

            Regards,

            Sam

            Comment

            Working...
            X