Using MockServletContext and ContextLoader with Spring 2.5 TestContext framework

Other than being, quite possibly, the longest blog post title so far here this topic had recently been a thorn in my side at work. Here I present the problem and a few different solutions to it. I’ve noticed also that on the spring forum a few others have been asking the same question so I hope this helps them too. Please note that code examples are deliberately kept as short as is necessary to illustrate the solutions. If adopting these solutions you may want to enhance the API yourself. Prior to stating the problem imagine the following ant based web application directory structure.

Project directory structure

project/
|-- java
`-- tests
|-- webapp
|   `-- WEB-INF
|       |-- bar-context.xml
|       |-- foo-context.xml
|       |-- classes
|       `-- lib

The Problem

The problem was to create a base class for spring integration tests that recognised the mock servlet context when loading spring context files and resources. Being a webapp the problem specification required that all paths to spring context files and resources must be relative to the web root (in this case ‘webapp/’) and should therefore start with ‘/WEB-INF/blah’. Also I wanted to do this using the new Spring 2.5 TestContext framework and JUnit4 so that I could use their improved APIs to make life easier and also because I didn’t want to base all tests written in the future on outdated api and versions.

Spring 2.0 and JUnit 3 Solution

import junit.framework.TestCase;

import org.springframework.core.io.FileSystemResourceLoader;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.support.XmlWebApplicationContext;

public abstract class AbstractJUnit3SpringIntegrationTest extends TestCase {

    protected static ConfigurableWebApplicationContext context;

    @Override
    protected void setUp() throws Exception {
        context = new XmlWebApplicationContext();
        context.setConfigLocations(new String[] { "/WEB-INF/foo-context.xml", "/WEB-INF/bar-context.xml" });
        context.setServletContext(new MockServletContext("/webapp", new FileSystemResourceLoader()));
        context.refresh();
        context.registerShutdownHook();
    }

    @Override
    protected void tearDown() throws Exception {
        context.close();
    }

    protected Object getBean(String beanName) {
        return context.getBean(beanName);
    }

}

Note that the above could be enhanced if necessary by allowing the child classes to specify the context file locations that they require and also by provided a strong typed generified getBean() method which would remove the need for casting from Object on every invocation. It is also possible to make the setup and teardown happen once only per class if desired. See elsewhere on this site for sample code.

Spring 2.0 and JUnit 4 Solution

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.springframework.core.io.FileSystemResourceLoader;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.support.XmlWebApplicationContext;

public abstract class AbstractJunit4SpringIntegrationTest {

    protected static ConfigurableWebApplicationContext context;

    @BeforeClass
    public static void setUpBeforeClass() throws Exception {

        String[] contexts = new String[] { "/WEB-INF/foo-context.xml" };
        context = new XmlWebApplicationContext();
        context.setConfigLocations(contexts);
        context.setServletContext(new MockServletContext("/webapp", new FileSystemResourceLoader()));
        context.refresh();

    }

    @AfterClass
    public static void tearDownAfterClass() {
        context.close();
    }

    protected Object getBean(String beanName) {
        return context.getBean(beanName);



    }

}

The difference with junit4 is that no inheritance is required and the context setup and teardown is done only once at the beginning and end respectively. The same suggestions for enhancement apply here as before.

Spring 2.5 and JUnit 4 Solution

There were a number of choices to make when implementing this using Spring 2.5 TestContext framework and JUnit 4. I will refer to the Spring 2.5 TestContext framework as TCF to relieve RSI somewhat. TCF API or documentation does not really offer a way of solving this problem out of the box. However by using lower level building blocks it is possible to achieve the desired effect. Let us look at how TCF works normally in the vanilla fashion.

import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/WEB-INF/foo-context.xml", "/WEB-INF/foo-context.xml" })
public abstract class AbstractSpringTCFTest {
}

Of course that won’t work because /WEB-INF is not on the classpath and a mock servlet context has not been registered. The following example shows how this can be done. The abstract spring base class needs a custom context loader applied to it.

import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = MockServletContextWebContextLoader.class, locations = {
        "/WEB-INF/foo-context.xml", "/WEB-INF/bar-context.xml" })
public abstract class AbstractSpringTCFTest {
}

The custom context loader is what sets a mock servlet context but also resolves certain api type compatibility issues. The key lines of code are highlighted below (which you won’t see if reading through rss).

import org.springframework.beans.factory.support.BeanDefinitionRegistry;
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.XmlWebApplicationContext;

public class MockServletContextWebContextLoader extends AbstractContextLoader {

    @Override
    public final ConfigurableApplicationContext loadContext(String... locations) throws Exception {
        ConfigurableWebApplicationContext context = new XmlWebApplicationContext();
        context.setServletContext(new MockServletContext("/webapp", new FileSystemResourceLoader()));
        context.setConfigLocations(locations);
        context.refresh();
        AnnotationConfigUtils.registerAnnotationConfigProcessors((BeanDefinitionRegistry) context.getBeanFactory());
        context.registerShutdownHook();
        return context;
    }

    @Override
    protected String getResourceSuffix() {
        return "-context.xml";
    }

}

So there we have it – the final solution using Spring 2.5 TCF and JUnit 4. I’ll be adding further enhancements and simplifications at a later date. Of course none of this hackery would be necessary if we were simply using classpath based resources. Classpath based access allows a uniform way of accessing resources of any kind through spring in any environment whether that be ant, eclipse or container. The spring resource locator types: classpath: and classpath*: will find items on the classpath with the latter asterisk based syntax searching for all matches on the filesystem including inside jars.

Update1: It turns out that there is a jira issue for this particular use case which is fantastic news and I believe we have Geoff, the commenter on this post, to thank for filing it. If you’d like to see this happen please add vote+comment to the jira issue. It is set to be completed by 3.0M2 which seems a little distant right now – it would have been great to have it in a point release. However better late than never.

Update2: Geoff please find my response below.

Originally the code in this post was implemented at my work place where it does work. However when I got around to trying it out in an isolated example project I too had the same problem – the lack of dependency injection and yes you can’t swap the refresh line and the annotations line due to a fatal exception.

Our work code base has extended certain spring classes to incorporate their own custom logic. So we are in fact using a custom subclass of XmlWebApplicationContext. However I did try using XmlWebApplicationContext and it still worked at work. As to why it works at my workplace and not in an isolated project I really haven’t had time to debug. However I think it’s great that this is finally filed as a jira which I’ve been meaning to do for quite some time because whether or not this problem can be solved with custom code I think that there should be native support for this in the spring framework. If I find out more about this issue I will post here and on forums to let you all know.

Update3: I’ve synchronised the minor differences between my work context loader and the example loader here (I’ve added the modifyLocations() method). Please retry and let me know. I’m unable to try this myself for the time being due to lack of time and a suitable environment.

Update4: Removed most recent modifications as they don’t work. It’s highly likely that this only works for me because at my workplace we are using a custom patched build of Spring and also our own classes which extend Spring (which I wasn’t aware of to this degree previously) so in order to get this work on a vanilla distribution please follow the forum link that GeoffM posted below or the jira ticket. Also vote on the jira ticket if you can. Thanks.

6 thoughts on “Using MockServletContext and ContextLoader with Spring 2.5 TestContext framework

  1. Hi Dhruba

    I posted to the Spring forums – asking how to create / access a WebApplicationContext instead of the default GenericApplicationContext within the Spring TestContext whilst doing some MVC integration testing. Sam Brannen pointed me to this blog.

    I followed the Spring 2.5 and JUnit 4 Solution but Dependancy Injection no longer works (which it does using the default loader) even with @TestExecutionListeners({DependencyInjectionTestExecutionListener.class}) declared.

    Any assistance, much appreciated.

    Thanks ………..Geoff

  2. Yeah I did try a couple of them out including that one. They didn’t work. The problem was not with the highlighter but the way the wordpress plugin integrated with the wordpress theme structure. I’ll need to try them once again and get them to work. Thanks.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s