Get ahead
VMware offers training and certification to turbo-charge your progress.
Learn more[callout title=Update Dec 19, 2012] The final Spring Framework reference documentation contains guidance on migration as well as a complete section on Spring MVC Test. [/callout]
Last week Juergen Hoeller announced
the release of Spring Framework 3.2 RC1 and Sam Brannen discussed exciting additions in its spring-test module such as support for WebApplicationContext
's and upcoming plans for loading a hierarchy of contexts. Today I will continue this subject and describe another exciting spring-test
addition. In 3.2 RC1 we've added first class support for testing Spring MVC applications both client-side and server-side.
The Spring MVC Test framework discussed here originates from a standalone project on Github, where features evolved for over a year with continuous feedback from many users. Thanks to all the early adopters, everyone who contributed, reported issues, commented, and everyone who blogged or spoke about it.
As of Spring 3.2 RC1, the code from the standalone project has been added to the Spring Framework and is available in the spring-test
module, under slightly modified package names, and with support for 3.2 specific features such as async requests and others. The standalone project will continue to exist for applications testing against Spring MVC 3.1.
With that out of the day, let's have a closer, more detailed look.
How do you test a Spring MVC controller today? Most likely through a simple unit test, possibly involving the MockHttpServletRequest
and -Response
. Pretty trivial to do but it doesn't test enough. Controllers have annotations that express how they are mapped, what request data needs to be extracted, converted, and validated, whether to write to the body of the response, how to handle exceptions, and on and on. All of what the framework does as a result of these annotations, remains untested if you only write simple unit tests.
What if you could re-write these controller unit tests but instead of invoking controllers directly, it would be done through the DispatcherServlet, just as it happens at runtime? And what if you could use a fluent API to specify the request to perform and the response you expect? All of that without the need for a servlet container. That's what Spring MVC Test does. Here is an example:
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("servlet-context.xml")
public class SampleTests {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = webAppContextSetup(this.wac).build();
}
@Test
public void getFoo() throws Exception {
this.mockMvc.perform(get("/foo").accept("application/json"))
.andExpect(status().isOk())
.andExpect(content().mimeType("application/json"))
.andExpect(jsonPath("$.name").value("Lee"));
}
}
[callout title=Static Imports]The fluent API relies on these static imports:
MockMvcBuilders.*
MockMvcRequestBuilders.*
MockMvcResultMatchers.*
For code completion assistance, add them as "favorite types" in the Eclipse preferences, or simply remember classes starting with MockMvc*
.[/callout]
As you can see we're using the new @WebAppConfiguration
annotation to load our Spring MVC configuration. Then we inject the resulting WebApplicationContext
into a test class field and use it to create a MockMvc
, which in turn is used perform requests and define expectations.
[callout title=Context Caching]The TestContext framework caches the loaded Spring configuration across the test suite and even across the JVM. Therefore the speed of the tests should be very optimal.[/callout]
Just like with existing controller unit tests, Spring MVC Test builds on the mock request and response from spring-test
and does not require a running servlet container. The main difference is that actual Spring MVC configuration is loaded through the TestContext framework and that the request is performed by actually invoking the DispatcherServlet
and all the same Spring MVC infrastructure that is used at runtime.
Also similar to existing controller unit tests, you may consider injecting controllers with mock services in order to focus on testing the web layer and avoid hitting a database for example. So instead of loading actual business and persistence services, you can load configuration that creates mocks. For example:
@Configuration
public class MyConfig {
@Bean
public FooService fooService() {
return Mockito.mock(FooService.class);
}
}
or in XML configuration:
<bean class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.example.FooService"/>
</bean>
Given that we're not running in an actual servlet container, just how much will and will not work? For the most part everything will work just like it does at runtime. You can even register Servlet filters, enabling things like Spring Security. Most rendering technologies like JSON/XML, Freemarker, Velocity, Thymeleaf, Excel, PDF, etc. All of that will work. The only rendering technology excluded is JSPs since since that requires a servlet container. For JSPs you can still verify the JSP the request was forwarded, what attributes are in the model, whether any exception was raised, and so on.
What's the idea behind client-side REST tests? If you have code using the RestTemplate
, you'll probably want to test it and to that you can target a running server or mock the RestTemplate. The client-side REST test support offers a third alternative, which is to use the actual RestTemplate
but configure it with a custom ClientHttpRequestFactory
that checks expectations against actual requests and returns stub responses.
RestTemplate restTemplate = new RestTemplate();
MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
mockServer.expect(requestTo("/greeting"))
.andRespond(withSuccess("Hello world", "text/plain"));
// use RestTemplate ...
mockServer.verify();
[callout title=Static Imports]The fluent API requires static imports:
MockRestRequestMatchers.*
MockRestResponseCreators.*
For code completion assistance, add them as "favorite types" in the Eclipse preferences, or simply remember classes starting with "MockRest*"
.[/callout]
As you can see we create an instance of RestTemplate
and pass it to MockRestServiceServer
to be configured. Then we define the characteristics of an expected request and provide a stub response to be returned. We could define any number of expected requests and stub responses. At the end of testing we can use the verify
method to check if all expected requests were executed.
There is a lot more we could discuss but it's probably best if you give it a try with your own project either using Spring Framework 3.2 RC1 or the standalone project on Github, which works with Spring Framework 3.1.
Recently I went through the exercise myself by adding a comprehensive set of tests for all controller methods of the spring-mvc-showcase. It was a useful exercise since it helped me find one bug. Therefore I expect it to be useful for others as well.
There are also many demo tests in the Spring Framework if you want more examples including tests with async requests, tests with filters, JSON responses, XML responses, and many others.