Home > Software Development > Spring MVC: Integration Testing Controllers

Spring MVC: Integration Testing Controllers

February 14, 2011 Leave a comment Go to comments

One of the greatest benefits of Spring MVC is that it removes your dependency on a servlet container. In theory, you should be able to test your controllers, and your entire web stack, from a testing harness like JUnit. In reality, you become just as dependent on the wonderful services offered by DispatcherServlet and a complete WebApplicationContext (request parameter binding, validation, model attributes, request mappings, and aspects such as Spring Security). In this article, I will show you how to create a mock servlet context and WebApplicationContext from within JUnit.

The trick to making this happen is to create a custom ContextLoader. This can be used by your JUnit tests as follows:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:spring.xml",
		loader=MockWebApplicationContextLoader.class)
@MockWebApplication(name="some-controller")
public class SomeControllerTests {
	...
}

The ContextLoader will be given the opportunity to load an application context and create a servlet environment for our tests before they are run. To facilitate this we create the MockWebApplication annotation to allow your tests to configure certain properties such as the servlet name and the path to the webapp directory.

The Context Loader

The source code for the MockWebApplicationContextLoader should be pretty straightforward. Let’s step through it quickly. First, we set up a ServletContext and ServletConfig using Spring’s mock implementations and the MockWebApplication as a source for their configuration. Then we create and configure an XmlWebApplicationContext. Finally, we create an instance of DispatcherServlet and configure it with our ServletConfig and ApplicationContext.

It is worth taking special note of the following code:

webApplicationContext.addBeanFactoryPostProcessor(new BeanFactoryPostProcessor() {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		beanFactory.registerResolvableDependency(DispatcherServlet.class, dispatcherServlet);
		// Register any other beans here, including a ViewResolver if you are using JSPs.
	}
});

Here, we register the DispatcherServlet as a bean in our context. This is so that it can be injected into our test classes and used to perform the integration tests. If you would like to register additional beans in your context this is where you would do it.

The View

If you are using Java Server Pages for your view layer you need to be aware of a limitation. JSPs are dependent on the servlet container and cannot be tested this way. As such, you will need to create and register a stub ViewResolver that does nothing. Furthermore, you will not be able to test your JSPs.

If at all possible, I would strongly recommend the use of an alternative technology such as Freemarker or Velocity. They are not dependent on a servlet container. As such, you’ll be able to test the template files from JUnit. This allows you to verify that all of your expressions, variable names, externalized messages, etc.

If you are using JSP tag libraries from FreeMarker, see this post for a small problem you will encounter and how to work around it.

In this example we will be using FreeMarker.

Example Tests

Let’s look at some example tests. The complete source code can be found here. Because we registered the DispatcherServlet in the context, it will be injected into our test classes:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:spring.xml",
		loader=MockWebApplicationContextLoader.class)
@MockWebApplication(name="some-controller")
public class SomeControllerTests {
	@Autowired
	private DispatcherServlet servlet;
}

Now we can test a request to GET /view?id=0:

@Test
public void viewTest() throws ServletException, IOException {
	MockHttpServletRequest request = new MockHttpServletRequest("GET", "/view");
	request.addParameter("id", "0");
	MockHttpServletResponse response = new MockHttpServletResponse();

	servlet.service(request, response);
	String results = response.getContentAsString().trim();

	Assert.assertEquals("<html><body>Hello World!</body></html>", results);
}

We do this by constructing a MockHttpServletRequest and MockHttpServletResponse (both a part of Spring) and passing them to the DispatcherServlet. We can get the results of the request (if we are not using JSPs) from the response object.

There are significant advantages to testing a web application this way. In particular, you can prepare your data model before issuing a request and inspect it afterwards for changes:

@Test
public void saveTest() throws ServletException, IOException {
	MockHttpServletRequest request = new MockHttpServletRequest("POST", "/");
	request.addParameter("name", "Ted");
	MockHttpServletResponse response = new MockHttpServletResponse();

	servlet.service(request, response);

	Assert.assertEquals("Ted", repository.find(1).getName());
}

And because we have a fully configured web stack we can test other services such as security. Here’s an example of accessing a secured portion of our website without authentication:

@Test(expected=NestedServletException.class)
public void secureFailedTest() throws ServletException, IOException {
	MockHttpServletRequest request = new MockHttpServletRequest("GET", "/secure/view");
	MockHttpServletResponse response = new MockHttpServletResponse();

	servlet.service(request, response);
}

Unlike using an external website testing tool we do not need to go through the tedious process of logging into account and keeping track of cookies. Instead, we can explicitly establish the user and its authorities directly with the Spring Security API:

@Test
public void secureTest() throws ServletException, IOException {
	SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("ted", "password"));

	MockHttpServletRequest request = new MockHttpServletRequest("GET", "/secure/view");
	MockHttpServletResponse response = new MockHttpServletResponse();

	servlet.service(request, response);
	String results = response.getContentAsString().trim();

	Assert.assertEquals("<html><body>Hello Secure!</body></html>", results);
}

Advantages over Other Tools

Most people, when tasked with creating automated tests for a web application, reach for tools such as JMeter that act as user agents interacting with the application. While these tools are very useful, for performance testing, our solution with JUnit has many advantages for functional testing.

In particular, because the tests are run within the same virtual machine as our application and within the same application context it is much easier to develop targeted, easy to maintain tests which can explicitly establish a model prior to a test and verify the model after the test. Furthermore, sequences of tests do not need to rely on parsing or scraping HTML output to determine subsequent URLs.

Finally, because the tests are developed using JUnit, they can run from continuous integration software, run from common build tools, and participate in code coverage.

References

I want to thank Tejus for giving me a head start on this.  Unfortunately, his prototype didn’t include complete source code and examples.  And I saw the chance to make some minor improvements on performance and functionality. So, I set out to write this.

  1. Tejus
    February 14, 2011 at 8:10 pm | #1

    Ted,

    Thanks for taking the time to put together a more complete example.

  2. Matt Young
    February 21, 2011 at 10:09 am | #2

    Ted -

    Thanks so much for putting this all together so clearly — and to you too Tejus for the original.

    -Matt

  3. Michael Bareja
    March 3, 2011 at 9:52 am | #3

    This is awesome. I really needed this solution. Before I was getting HandlerExecutionChain from HandlerMapping bean. When I test with your MockApplicationContextLoader I am finally able to test the whole request with exception handling, view resolving, view generation etc.

    Thanks a lot

  4. Marek Kowalski
    May 17, 2011 at 4:51 am | #4

    First of all, thanks for great article!

    Second of all, could you please tell me is it possible to load MockWebApplicationContext from web.xml? I have many xml config files in web.xml. Those files are listed in of web app and of my DispatcherServlet.
    I would appreciate any tips :)

    -Marek

  5. Marek Kowalski
    May 17, 2011 at 4:59 am | #5

    I guess auto-formatting cut out xml tags. Last sentence should look like: “those files are listed in context-param/contextConfigLocation of web app and init-param/contextConfigLocation of my DispatcherServlet.”

    I really would appreciate any tips how to get it done :)

    -Marek

  6. May 17, 2011 at 8:55 am | #6

    No, your web.xml is completely uninvolved in the testing process. That is very important, because it means you cannot test servlet filters and the like. You would have to set up and configure those by hand.

    What you can do, however, is place your @RunWith, @ContextConfiguration, and @MockWebApplication in a base class that all of your tests inherit from. That way all of your test configuration is centralized.

    Finally, you can use the import tag in your test’s spring configuration to import other configuration files.

  7. May 17, 2011 at 8:58 am | #7

    I should have also stated that the locations field of the @ContextConfiguration can accept an array of values.

  8. Marek Kowalski
    May 18, 2011 at 6:48 am | #8

    Thank you!

  9. Rossen
    June 19, 2011 at 12:50 pm | #9

    Hi, I just wanted to mention that a better alternative to testing Spring MVC controllers is coming:
    https://github.com/SpringSource/spring-test-mvc.

    It’s still early days but you can test pretty much everything server-side (except JSPs). You can track the progress. Your feedback would be very helpful!

    Rossen

  10. June 19, 2011 at 5:19 pm | #10

    This looks very interesting. According to the summary, this project allows one to build a controller test using a factory pattern very similar to EasyMock, et al. We will be creating a complete set of controller tests for our application in the next 12 months. I will keep a close eye on this project! Thank you.

  11. July 24, 2011 at 11:38 pm | #11

    Thanks a lot for this great example.

    To get this code to work from command line maven I made the following POM changes:
    1. Add the EBR External Release Repository to the repositories section
    2. Change the scope on the hibernate-validator dependency to compile
    3. Remove the compilerId config from the maven compiler config
    And
    4. Renamed the SomeControllerTests.java to SomeControllerTest.java (singular).

  12. July 25, 2011 at 7:28 pm | #12

    Thank you very much for the feedback! Yes, you will need to add the ERB to your repository list. I need to write an article about how to use Spring and Maven effectively. It is not trivial. I used Eclipse to build and test the project; so I never noticed the hibernate-validator dependency or the compilerId issue. Having to rename SomeControllerTests to SomeControllerTest was a result of the JUnit configuration. I guess the default in Maven is the 3.0 style of classes ending in Test, where as Eclipse uses the annotation driven approach. I will update the project as soon as possible and start double-checking these projects run outside of Eclipse. And thank you again for your contribution!

    Update: the changes have been posted to the Google Code project.

  13. August 19, 2011 at 8:52 am | #13

    Ted,

    Thanks you very much for this great example. But, I was having problem while executing the same solution on my project ( depends on jsp page).

    “If you are using Java Server Pages for your view layer you need to be aware of a limitation. JSPs are dependent on the servlet container and cannot be tested this way. As such, you will need to create and register a stub ViewResolver that does nothing. Furthermore, you will not be able to test your JSPs.”

    Based on this, I tried writing Stub View Resolver which does nothing but still, no luck. it throws null pointer exception as soon as controller try to return modelandview Object.

    Have you got any idea about this??

  14. August 19, 2011 at 7:30 pm | #14

    I can’t tell you without seeing your code. Java, however, will likely tell you exactly what is wrong. Look at the stack trace and track down where the exception is occurring and identify what is null. That will tell you what you need to do.

  15. August 20, 2011 at 3:23 am | #15

    Thanks Ted, I found out the issue. It was mock response which I was sending null. Its working now :)

  16. August 20, 2011 at 7:08 am | #16

    Great. I am glad you found it!

  17. Rud
    September 7, 2011 at 8:41 am | #17

    Ted, my DispatcherServlet is never getting wired up. Any thoughts on how to debug this?

  18. September 7, 2011 at 8:54 am | #18

    Can you inject/autowire other resources? If not, then you probably have a spring configuration problem. If so, then the problem is probably in your MockWebAppContextLoader. If you are using Autowire or Inject (like the example above), then ensure that you have enabled context:component-scan and that it is set to scan the package that includes your tests. Second, double-check your code. Ensure that you are adding the servlet to the context and that you are calling refresh on the context. You might put a log statement at line 56 to ensure that the servlet is being added.

  19. Vathanak
    September 23, 2011 at 6:38 pm | #19

    Thanks Ted for this. But, I have a problem. My url mapping for my DispatcherServlet in web.xml file is /api/* and the mapping url on my controller is v10/search. Using your test, there mapping path is not found by requesting /api/v10/search but it’s working in real environment. Under the test environment, i can only make a request /v10/search. Do you know what is the problem? Thanks.

  20. September 23, 2011 at 6:55 pm | #20

    Did you try @MockWebApplication(name=”api”) on your test class?

  21. Vathanak
    September 25, 2011 at 11:11 am | #21

    I post it again.

    My servlet name is “photo-upload” and mapping url is “/api/*”. The request mapping on my controller is “/v10/monitor”.

    I just tried what you said “api”, “/api”, or my servlet name “photo-upload”. But, still don’t any luck.

  22. September 25, 2011 at 5:40 pm | #22

    I am sorry. I wasn’t thinking clearly when I answered your last question. The strategy in this article only mocks the DispatchServlet, not the entire servlet container. Normally, the servlet container strips the context path (/api) from the url and passes the request to the DispatchServlet. Here, since we are using the DispatchServlet directly, you have to remove the /api from the URL yourself. Similarly, the lack of a servlet container also means that servlet filters will not be executed during testing and that you cannot run JSPs.

  23. Vathanak
    September 25, 2011 at 11:22 pm | #23

    Okay, thanks Ted. I modified the MockWebApplication to add servletContextPath so that I can use it as:
    @MockWebApplication(name = “photo-upload”, servletContextPath = “/api/”)

    And in the class MockWebApplicationContextLoader, I override the method getHandler() of DispatcherServlet as following:

    protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception {
    String uri = request.getRequestURI();
    if (request instanceof MockHttpServletRequest && uri.startsWith(servletContextPath)) {
    MockHttpServletRequest mockRequest = (MockHttpServletRequest) request;
    mockRequest.setRequestURI(uri.substring(servletContextPath.length() – 1, uri.length()));
    return super.getHandler(mockRequest, cache);
    }
    return super.getHandler(request, cache);
    }

    Then, in the test class, i still can use the request uri /api/v10/monitor. My controllers need the request uri like the real one to do something else.

  24. September 26, 2011 at 7:24 am | #24

    Wonderful. Thank you for the contribution. As soon as I can, I will merge it into the code.

  25. Timo Sand
    December 1, 2011 at 9:17 am | #25

    This just doesn’t work for me. It keeps telling me, that I don’t have a mapping for the calls I am making. I don’t get it. But when I deploy the app and test the same calls, then it works.

  26. December 1, 2011 at 11:41 am | #26

    The first thing I would suggest you check is your Spring test configuration. Make sure that you have context:component-scan and mvc:annotation-driven declared and configured properly.

  27. Vathanak
    December 4, 2011 at 5:58 am | #27

    Hi Timo, I don’t see you feedback to Ted yet so I think your problem may not be solved yet. If the mapping url on the controller is “v10/search” (no slash at the begining) the servletContextPath should contain a slash at the end:
    @MockWebApplication(name = “photo-upload”, servletContextPath = “/api/”)

    But if the mapping url on the controller has a slash at the begining, you should remove the slash at the end from the servletContextPath.

    This is only thing i can think of. You may have double slashes.

  28. Mate Lakat
    December 18, 2011 at 4:26 pm | #28

    Ted, thank you very much for this post. I looked at spring-test-mvc, however, that seems to require you to be on the latest spring release, it was asking for org.springframework.web.servlet.FlashMapManager, and that seems to be a 3.1 feature. So I copied in your classes, and they work well.

  29. December 19, 2011 at 10:37 am | #29

    That is interesting. I developed this example and have used it for over a year under Spring version 3.0. I suspect he might have had multiple Spring versions in your class path. This is very easy to do if you are using maven.

  30. Mate Lakatt
    December 19, 2011 at 4:31 pm | #30

    Hi Ted, sorry for the confusion. Rossen mentioned the spring-test-mvc project, and that is the one, that requires Spring 3.1 goodies (at least the snapshot, I pulled yesterday). Your classes work perfectly in my 3.0 env, so thanks again!

    Rossen :
    Hi, I just wanted to mention that a better alternative to testing Spring MVC controllers is coming:
    https://github.com/SpringSource/spring-test-mvc.
    It’s still early days but you can test pretty much everything server-side (except JSPs). You can track the progress. Your feedback would be very helpful!
    Rossen

  31. December 20, 2011 at 12:09 am | #31

    I understand now. Thank you for the clarification.

  32. Ravi Kishore
    May 17, 2012 at 3:34 am | #32

    Hi Ted,

    I need to test my controller having methods of request type(PUT or DELETE). I have implemeneted the above approach and its working fine for request type GET.

    I have tried with request type as “PUT” or “DELETE”, while run of test has shown a warning message as

    [WARN] Request method ‘DELETE’ not supported

    Can you help me out, how to overcome or workout this problem. One more is,

    I have methods “GET” with passing of the some of the parameters. Event this is also not working.

    Can you give any examples.

    Thanks in advance.

    Regards,
    Ravi Kishore. V

  33. May 18, 2012 at 11:07 pm | #33

    Please forgive the lateness of my reply. DELETE should work just as well as GET or POST (j2ee has full support for DELETE, browsers are the ones that lack support for DELETE). So, I suspect there is something wrong with your setup. Unfortunately, without more information, I cannot help guide you.

  34. Ravi
    May 22, 2012 at 4:01 am | #34

    Ted,
    Thanks for the reply. By adjusting the URL pattern for request, i am able to resolve the issue.

    Thank you

  35. May 22, 2012 at 9:09 am | #35

    I am glad you are able to solve your problem. URL mapping is the trickiest part of dealing with servlets in general.

  1. April 20, 2011 at 10:23 am | #1

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com 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

Follow

Get every new post delivered to your Inbox.