Getting Started With Spring’s MVC Test Framework – Part 1
Newly promoted to the main Spring framework is the Spring MVC Test Framework, which the Guys at Spring claim is a “first class JUnit support for testing client and server side Spring MVC code through a fluent API”1. In this and my next blog, I’m going to take a look at Spring’s MVC Test Framework and apply it to some of my existing sample code to figure out whether or not it does what it says on the tin.
The API has been designed with two ways of setting up server side tests. These are firstly, with a Spring context file and secondly, programmatically without a context file. The Guys at Spring refer to the programatic method as ‘standalone’ mode.
Setting tests up programmatically seems to be more akin to unit testing and is best used to unit test a particular controller class in isolation from its collaborators. On the other hand, the act of loading a Spring context file is really integration testing and is more suitable for end to end tests.
You can find a full list of blogs on testing techniques here.
If you’re like me, then you’ll already be using an existing framework such as Mockito or Easymock to test your controllers. The usual Mockito/Easymock approach is to instantiate your controller, inject mock or stubbed dependencies and then call the method under test noting the return value or verifying mock method calls.
The Spring Mvc Test framework takes a different approach to other mock frameworks in that it loads the Spring DispatcherServlet to emulate the operation of a web container. The controller under test is then loaded into the Spring context and accessed by the DispatcherServlet just as it would be in ‘real life’.
The benefit of this approach is that it allows you to test a controller as a controller rather than as a POJO. This means that the controller’s annotations are processed and taken into account, validation is carried out and methods are called in the right order.
Whether or not you agree with this approach and depends upon your view of testing techniques. If you’re of the view that every class / method you test should be isolated to the nth degree and every test totally atomic, then maybe this isn’t for you. If you’re somewhat more pragmatic and can see the benefit of testing a controller as… a controller then this framework maybe of interest.
Being somewhat different in approach to Mockito and Easymock, the downside is that the code looks different to these older, more established technologies. It relies heavily on the builder pattern for constructing matchers, request builders and handlers and once you get the hang of it, it all makes sense. I suspect the that motivation for using the builder pattern is to simplify the setup of the mock HttpServletRequest
and the interrogation of the mock HttpServletResponse
objects, which by definition can be quite tricky.
In this blog I’m going to take a look at the Spring API’s programmatic/standalone technique comparing it with a similar Mockito based unit test.
In order to speed things up I’m using the Blue Peter method of “here’s one I prepared earlier”, and taking the FacebookPostsController
from my Facebook blog for which I’ll write two unit test classes: the first using Mockito and the other using the Spring Mvc Test API.
The controller code looks like this:
@Controller public class FacebookPostsController { private static final Logger logger = LoggerFactory .getLogger(FacebookPostsController.class); @Autowired private SocialContext socialContext; @RequestMapping(value = "posts", method = RequestMethod.GET) public String showPostsForUser(HttpServletRequest request, HttpServletResponse response, Model model) throws Exception { String nextView; if (socialContext.isSignedIn(request, response)) { List<Post> posts = retrievePosts(); model.addAttribute("posts", posts); nextView = "show-posts"; } else { nextView = "signin"; } return nextView; } private List<Post> retrievePosts() { Facebook facebook = socialContext.getFacebook(); FeedOperations feedOps = facebook.feedOperations(); List<Post> posts = feedOps.getHomeFeed(); logger.info("Retrieved " + posts.size() + " posts from the Facebook authenticated user"); return posts; } }
I’m not going into the background of this code as it’s available in the Facebook blog; however, to summarise, the Facebook sample app accesses the user’s Facebook account and displays their news feed in the sample app. To do this, the FacebookPostsController
checks the SocialContext
class to ascertain whether or not the user is logged in to their Facebook account. If the user is logged in to their Facebook account, then the controller retrieves the user’s posts and adds them to the model for display. If, on the other hand, the user isn’t logged in then they’re directed to the sign in page.
Each of the two unit test classes will contain three public methods:
setup()
, testShowPostsForUser_user_is_not_signed_in
and testShowPostsForUser_user_is_signed_in
each of which I’ll examine in turn.
As you might expect, the tests testShowPostsForUser_user_is_not_signed_in
and testShowPostsForUser_user_is_signed_in
, are used to test the cases where the user is and isn’t logged in to their Facebook account.
The ‘Standard’ Mockito Test
@Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); instance = new FacebookPostsController(); ReflectionTestUtils.setField(instance, "socialContext", socialContext); }
The setup code is fairly straight forward and contains three simple steps:
- Initialize the mock objects using
MockitoAnnotations.initMocks(this)
. - Create a new instance of
FacebookPostsController
, the object under test. - Inject the mock
SocialContext
into theFacebookPostsController
.
@Test public void testShowPostsForUser_user_is_not_signed_in() throws Exception { when(socialContext.isSignedIn(request, response)).thenReturn(false); String result = instance.showPostsForUser(request, response, model); assertEquals("signin", result); }
The testShowPostsForUser_user_is_not_signed_in
method configures the mock SocialContext
to return false
when its isSignedIn()
method is called. This means that all that’s left to do is to assert that the showPostsForUser(...)
method returns "signin"
directing the user to the sign in page.
@Test public void testShowPostsForUser_user_is_signed_in() throws Exception { when(socialContext.isSignedIn(request, response)).thenReturn(true); when(socialContext.getFacebook()).thenReturn(facebook); when(facebook.feedOperations()).thenReturn(feedOps); List<Post> posts = Collections.emptyList(); when(feedOps.getHomeFeed()).thenReturn(posts); String result = instance.showPostsForUser(request, response, model); verify(model).addAttribute("posts", posts); assertEquals("show-posts", result); }
The testShowPostsForUser_user_is_signed_in
is somewhat more complex. After configuring the mock SocialContext
to return true
when its isSignedIn()
method is called there are also an extra four lines of code which ensure that a list of posts
is returned from the mock Facebook feed and added to the mock Model
. After calling the showPostsForUser(...)
there are two additional steps to complete: verifying that the mock Model
contains the list of posts
and asserting that the return value from showPostsForUser(...)
is "show-posts"
.
Next, the Spring MVC Test framework code; however, before getting started you need to add the following dependency to your POM file:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${org.springframework-version}</version> <scope>test</scope> </dependency>
The Spring MVC Test
The Spring MVC Test framework version runs the same two tests, but in a different way…
@Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); FacebookPostsController instance = new FacebookPostsController(); ReflectionTestUtils.setField(instance, "socialContext", socialContext); mockMvc = MockMvcBuilders.standaloneSetup(instance).build(); }
If you take a look at the code above, you can see that, as far as the setup(...)
goes, it looks fairly similar to the straight forward Mockito code above. Like the Mockito based test, the first step is to initialize the mock objects using
MockitoAnnotations.initMocks(this)
, which is followed by creating a new instance of FacebookPostsController
into which the mock SocialContext
is injected. This time, however, the FacebookPostsController
has been relegated in status to a local variable as the whole point of the setup is to create an instance of Spring’s MockMvc
, which is used to perform the tests. The mockMvc
is created by calling MockMvcBuilders.standaloneSetup(instance).build()
where instance
is the FacebookPostsController
object we’re testing.
@Test public void testShowPostsForUser_user_is_not_signed_in() throws Exception { HttpServletRequest request = anyObject(); HttpServletResponse response = anyObject(); when(socialContext.isSignedIn(request, response)).thenReturn(false); MockHttpServletRequestBuilder getRequest = get("/posts").accept(MediaType.ALL); ResultActions results = mockMvc.perform(getRequest); results.andExpect(status().isOk()); results.andExpect(view().name("signin")); }
Like the Mockito version, the testShowPostsForUser_user_is_not_signed_in
method configures the mock SocialContext
to return false
when its isSignedIn()
method is called. This time the next step is to create something called a MockHttpServletRequestBuilder
using the static method
MockMvcRequestBuilders.get(...)
and the builder pattern. This is passed into the mockMVC.perform(...)
method where it’s used to create a MockHttpServletRequest
object that’s used to define a starting point for the test. In this test, all I’ve done is to pass in the "/posts"
url and set the input as ‘any’ media type. You can configure lots of other request object attributes using methods such as contentType()
, contextPath()
, cookie()
etc. For more information take a look at the Spring javadoc for MockHttpServletRequest
The mockMvc.perform()
method returns a ResultActions
object. This seems to be a wrapper around the actual MvcResult
. The ResultsActions
is a convenience object used to assert the result of the test in the same way as JUnit’s assertEquals(...)
or Mockito’s verify(..)
methods. In this case, I’m checking that the resulting Http status is ok (i.e. 200) and that the next view will "signin"
.
The difference between this test and the Mockito only version is that you’re not directly testing the result of the method call to your test instance; you’re testing the HttpServletResponse
object that the call to your method generates.
@Test public void testShowPostsForUser_user_is_signed_in() throws Exception { HttpServletRequest request = anyObject(); HttpServletResponse response = anyObject(); when(socialContext.isSignedIn(request, response)).thenReturn(true); when(socialContext.getFacebook()).thenReturn(facebook); when(facebook.feedOperations()).thenReturn(feedOps); List<Post> posts = Collections.emptyList(); when(feedOps.getHomeFeed()).thenReturn(posts); mockMvc.perform(get("/posts").accept(MediaType.ALL)).andExpect(status().isOk()) .andExpect(model().attribute("posts", posts)) .andExpect(view().name("show-posts")); }
The Spring Test version of the testShowPostsForUser_user_is_signed_in
method is very similar the Mockito version in the way that the test is prepared with the SocialContext.isSignedIn()
method configured to return true
and the feedOps.getHomeFeed()
configured to return a list of posts
. The Spring Mvc Test part of this method is almost identical to the testShowPostsForUser_user_is_not_signed_in
version described above, except that this time it checks for a next view name of "show-posts"
rather than "sign-in"
using andExpect(view().name("show-posts")
. The code style I’ve used here is somewhat different to the style I used above and is the style preferred by the Guys at Spring. You can find many more examples of this style on Github if you get hold of the Spring MVC Showcase app.
So, what can you conclude from this comparison? Well to be fair it’s not a real comparison – the Spring MVC Test API, whilst building upon the standard Mockito technique, takes a different approach, creating a framework that’s designed to solely test Spring MVC controllers in a mock-up of their native runtime environment.
Whether or not this is useful to you, I’ll let you decide. It does have the advantage that controllers are treated as controllers and not POJOs, which means that they’re tested more thoroughly. From a personal point of view, I like the idea of loading Spring config files and using it for integration tests, which is something I’ll be looking at in my next blog.
- 1See: http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/testing.html#spring-mvc-test-framework
- The code for this blog is available on GitHub: https://github.com/roghughe/captaindebug/tree/master/facebook