VMware offers training and certification to turbo-charge your progress.Learn more
On Monday I announced the release of the first milestone of Spring Test MVC HtmlUnit with the promise of a blog series that would introduce it. This is the first of a four part blog series introducing Spring Test MVC HtmlUnit. The series outline can be seen below:
The most obvious question that comes to mind is "Why do I need this?" The answer is best found by exploring a very basic sample application. Assume you have a Spring MVC web application that allows CRUD operations on a Message object. The application also allows paging through all messages. How would you go about testing it?
With Spring MVC Test, we can easily test if we are able to create a
MockHttpServletRequestBuilder createMessage = post("/messages/") .param("summary", "Spring Rocks") .param("text", "In case you didn't know, Spring Rocks!"); mockMvc.perform(createMessage) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/messages/123"));
What if we want to test our form view that allows us to create the message? For example, assume our form looks like the following snippet:
<form id="messageForm" action="/messages/" method="post"> <div class="pull-right"><a href="/messages/">Messages</a></div> <label for="summary">Summary</label> <input type="text" class="required" id="summary" name="summary" value="" /> <label for="text">Message</label> <textarea id="text" name="text"></textarea> <div class="form-actions"> <input type="submit" value="Create" /> </div> </form>
How do we ensure that our form will produce the correct request to create a new message? A naive attempt would look like this:
mockMvc.perform(get("/messages/form")) .andExpect(xpath("//input[@name='summary']").exists()) .andExpect(xpath("//textarea[@name='text']").exists());
This test has some obvious problems. If we updated our controller to use the parameter "message" instead of "text", our test would would incorrectly pass. To resolve this we could combine our two tests:
String summaryParamName = "summary"; String textParamName = "text"; mockMvc.perform(get("/messages/form")) .andExpect(xpath("//input[@name='" + summaryParamName + "']").exists()) .andExpect(xpath("//textarea[@name='" + textParamName + "']").exists()); MockHttpServletRequestBuilder createMessage = post("/messages/") .param(summaryParamName, "Spring Rocks") .param(textParamName, "In case you didn't know, Spring Rocks!"); mockMvc.perform(createMessage) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/messages/123"));
This would reduce the risk of our test incorrectly passing, but there are still some problems:
To resolve the issues above we could perform integration testing, but this has some obvious drawbacks. Consider testing the view that allows us to page through the messages. We might need the following tests:
To set these tests up we would need to ensure our database contained the proper messages in it. This leads to a number of problems:
These problems do not mean that we should abandon integration testing all together. Instead, we can reduce the number of integration tests by moving our detailed tests to use mock services which will perform much faster. We can then use fewer integration tests that validate simple workflows to ensure that everything works together properly.
So how can we provide a balance between testing the interactions of our pages and still get performance? I'm sure you already guessed it...Spring Test MVC HtmlUnit will allow us to:
NOTE: Just as with Spring MVC Test, the HtmlUnit integration will work with templating technologies that do not rely on a Servlet Container (i.e. Thymeleaf, Freemarker, Velocity, etc). It does not work with JSPs since they rely on the Servlet Container.
I hope this post excites you for my next post which discusses how we can solve some of these problems by integrating Spring Test MVC and HtmlUnit.
If you have feedback on this blog series or the Spring Test MVC HtmlUnit, I encourage you to reach out via github issues or ping me on twitter @rob_winch. Of course the best feedback comes in the form of contributions.