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 the FacebookPostsController.
@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
1 comment:
I find the new Spring MVC Test framework really interesting. It lets me to decrease the number of heavy WebDriver tests and test a lot of "framework infrastructure" (annotations, context). It also allows me to have tests for always "undertested" controllers - I mean they usually do so little (i.e. they delegate) that there is not so much unit tests can verify.
Thanks for an interesting article!
Post a comment