diff --git a/src/reference/docbook/mvc.xml b/src/reference/docbook/mvc.xml index 24b5c877d7f..2363b808ea7 100644 --- a/src/reference/docbook/mvc.xml +++ b/src/reference/docbook/mvc.xml @@ -2358,6 +2358,15 @@ public String myHandleMethod(WebRequest webRequest, Model model) { request. + +
+ Testing Controllers + + The spring-test module offers first class support + for testing annotated controllers. + See . +
+
diff --git a/src/reference/docbook/new-in-3.2.xml b/src/reference/docbook/new-in-3.2.xml index c24fd5c0cc5..19a1f9dc9d0 100644 --- a/src/reference/docbook/new-in-3.2.xml +++ b/src/reference/docbook/new-in-3.2.xml @@ -49,10 +49,7 @@ fluent API and without a servlet container. Server-side tests involve use of the DispatcherServlet while client-side REST tests rely on the RestTemplate. - See the following presentation for more information before - documentation is added: - - "Testing Web Applications with Spring 3.2". + See .
diff --git a/src/reference/docbook/testing.xml b/src/reference/docbook/testing.xml index dd421856978..93ffe9e8b63 100644 --- a/src/reference/docbook/testing.xml +++ b/src/reference/docbook/testing.xml @@ -2399,6 +2399,531 @@ public class SimpleTest { +
+ Spring MVC Test Framework + + + Standalone project + + Before inclusion in Spring Framework 3.2, the Spring MVC Test + framework had already existed as a separate project on Github where + it grew and evolved through actual use, feedback, and the contribution of + many. + The standalone + spring-test-mvc project + is still available on Github and can be used in conjunction with + Spring Framework 3.1.x. Applications upgrading to 3.2 should replace + the spring-test-mvc dependency with a dependency on + spring-test. + + The spring-test module uses a + different package org.springframework.test.web + but otherwise is nearly identical with two exceptions. + One is support for features new in 3.2 (e.g. async web requests). + The other relates to the options + for creating a MockMvc instance. In Spring + Framework 3.2, this can only be done through the TestContext framework, + which provides caching benefits for the loaded configuration. + + + The Spring MVC Test framework provides + first class JUnit support for testing client and server-side + Spring MVC code through a fluent API. Typically it loads + the actual Spring configuration through the + TestContext framework and always uses the + DispatcherServlet to process requests + thus approximating full integration tests without requiring + a running servlet container. + + Client-side tests are + RestTemplate-based and allow tests for code + that relies on the RestTemplate without requiring a + running server to respond to the requests. + + +
+ Server-Side Tests + + Before Spring Framework 3.2, the most likely way to test a Spring MVC + controller was to write a unit test that instantiates the controller, + injects it with mock or stub dependencies, and then calls its methods + directly, using a MockHttpServletRequest and + MockHttpServletResponse where necessary. + Although this is pretty easy to do, controllers have many annotations, + and much remains not tested. Request mappings, data binding, + type conversion, and validation are just a few examples of what isn't tested. + Furthermore, there are other types of annotated methods + such as @InitBinder, + @ModelAttribute, and + @ExceptionHandler that get invoked as part + of request processing. + + The idea behind Spring MVC Test is to be able to + re-write those controller tests by performing actual requests, and generating + responses, as they would be at runtime, along the way invoking controllers + through the Spring MVC DispatcherServlet. Controllers + can still be injected with mock dependencies, so tests can remain focused + on the web layer. + + Spring MVC Test builds on the familiar "mock" implementations + of the Servlet API available in the spring-test module. + This allows performing requests and generating responses without + the need for running in a Servlet container. For the most part everything should + work as it does at runtime with the exception of JSP rendering, which is + not available outside a Servlet container. Furthermore, + if you are familiar with how the MockHttpServletResponse + works, you'll know that forwards and redirects are not actually executed. + Instead "forwarded" and "redirected" URLs are saved and can be asserted + in tests. This means if using JSPs, you can verify the JSP page + the request was forwarded to. + + All other means of rendering including + @ResponseBody methods and + View types (besides JSPs) such as + Freemarker, Velocity, Thymeleaf, and others for rendering HTML, + JSON, XML, and so on should work as expected and the response will + contain the generated content. + + + Below is an example of a test requesting account information in JSON format: + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@RunWith(SpringJUnit4ClassRunner.class) +@WebAppConfiguration +@ContextConfiguration("test-servlet-context.xml") +public class ExampleTests { + + @Autowired + private WebApplicationContext wac; + + private MockMvc mockMvc; + + @Before + public void setup() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + } + + @Test + public void getAccount() throws Exception { + this.mockMvc.perform(get("/accounts/1").accept("application/json;charset=UTF-8")) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/json")) + .andExpect(jsonPath("$.name").value("Lee"); + } + +} + + The test relies on the WebApplicationContext + support of the TestContext framework. It loads Spring configuration from + an XML config file located in the same + package as the test class (also supports Java config) and injects + the created WebApplicationContext + into the test so a MockMvc instance can be + created with it. + + The MockMvc is then + used to perform a request to "/accounts/1" + and verify the resulting + response status is 200, the response content type is + "application/json", and response content + has a JSON property called "name" with the value "Lee". + JSON content is inspected with the help of Jayway's + JsonPath project. + There are lots of other options for verifying the + result of the performed request and those will be discussed later. + +
+ Static Imports + + The fluent API in the example above requires a few static imports + such as MockMvcRequestBuilders.*, + MockMvcResultMatchers.*, and + MockMvcBuilders.*. An easy way to find + these classes is to search for types matching + "MockMvc*". + If using Eclipse, be sure to add them as + "favorite static members" in the Eclipse preferences under + Java -> Editor -> Content Assist -> Favorites. + That will allow use of content assist after typing + the first character of the static method name. + Other IDEs (e.g. IntelliJ) may not require any additional + configuration. Just check the support for code completion + on static members. + +
+ +
+ Setup Options + + The goal of server-side test setup is to create + an instance of MockMvc that can be used + to perform requests. There are two main options. + + The first option is to point to Spring MVC configuration + through the TestContext framework, + which loads the Spring configuration and injects a + WebApplicationContext into the + test to use to create a MockMvc: + +@RunWith(SpringJUnit4ClassRunner.class) +@WebAppConfiguration +@ContextConfiguration("my-servlet-context.xml") +public class MyWebTests { + + @Autowired + private WebApplicationContext wac; + + private MockMvc mockMvc; + + @Before + public void setup() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + } + + // ... + +} + + The second option is to simply register a controller + instance without loading any Spring configuration. + Instead basic Spring MVC configuration suitable for testing + annotated controllers is automatically created. The created + configuration is comparable to that of the MVC Java config + (and the MVC namespace) and can be customized to a degree + through builder-style methods: + +public class MyWebTests { + + private MockMvc mockMvc; + + @Before + public void setup() { + this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build(); + } + + // ... + +} + + Which option should you use? + + The "webAppContextSetup" loads + the actual Spring MVC configuration + resulting in a more complete integration test. Since the + TestContext framework caches the loaded + Spring configuration, it helps to keep tests running fast + even as more tests get added. Furthermore, you can inject mock + services into controllers through Spring configuration, + in order to remain focused on testing the web layer. + Here is an example of declaring a mock service with Mockito: + + +<bean id="accountService" class="org.mockito.Mockito" factory-method="mock"> + <constructor-arg value="org.example.AccountService"/> +</bean> + + + Then you can inject the mock service into the test + in order set up and verify expectations: + +@RunWith(SpringJUnit4ClassRunner.class) +@WebAppConfiguration +@ContextConfiguration("test-servlet-context.xml") +public class AccountTests { + + @Autowired + private WebApplicationContext wac; + + private MockMvc mockMvc; + + @Autowired + private AccountService accountService; + + // ... + +} + + The "standaloneSetup" on the other + hand is a little closer to a unit test. It tests one controller at a time, + the controller can be injected with mock dependencies manually, + and it doesn't involve loading Spring configuration. + Such tests are more focused in style and make it easier to see + which controller is being tested, whether any specific Spring MVC + configuration is required to work, and so on. The "standaloneSetup" is also + a very convenient way to write ad-hoc tests to verify some + behavior or to debug an issue. + + Just like with integration vs unit testing, there is no right or + wrong answer. Using the "standaloneSetup" does imply the + need for some additional "webAppContextSetup" tests to verify the + Spring MVC configuration. Alternatively, you can decide write all + tests with "webAppContextSetup" and always test against actual + Spring MVC configuration. +
+ +
+ Performing Requests + + To perform requests, use the appropriate HTTP + method and additional builder-style methods corresponding + to properties of MockHttpServletRequest. + For example: + + +mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON)); + + + In addition to all the HTTP methods, you can also perform file + upload requests, which internally creates an instance of + MockMultipartHttpServletRequest: + + +mockMvc.perform(fileUpload("/doc").file("a1", "ABC".getBytes("UTF-8"))); + + + Query string parameters can be specified in the URI template: + + +mockMvc.perform(get("/hotels?foo={foo}", "bar")); + + + Or by adding Servlet request parameters: + + +mockMvc.perform(get("/hotels").param("foo", "bar")); + + + If application code relies on Servlet request parameters, and + doesn't check the query string, as is most often the case, + then it doesn't matter how parameters are added. Keep in mind + though that parameters provided in the URI template will be decoded + while parameters provided through the param(...) + method are expected to be decoded. + + In most cases it's preferable to leave out the context path and + the servlet path from the request URI. If you must test with the full + request URI, be sure to set the contextPath and servletPath + accordingly so that request mappings will work: + + +mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main")) + + + Looking at the above example, it would be cumbersome to set + the contextPath and servletPath with every peformed request. + That's why you can define default request + properties when building the MockMvc: + + +public class MyWebTests { + + private MockMvc mockMvc; + + @Before + public void setup() { + mockMvc = standaloneSetup(new AccountController()) + .defaultRequest(get("/") + .contextPath("/app").servletPath("/main") + .accept(MediaType.APPLICATION_JSON).build(); + } + +} + + The above properties will apply to every request performed through + the MockMvc. If the same property is also specified + on a given request, it will override the default value. That is why, the + HTTP method and URI don't matter, when setting default request properties, + since they must be specified on every request. + +
+ +
+ Defining Expectations + + Expectations can be defined by appending one or more + .andExpect(..) after call to perform the request: + + +mockMvc.perform(get("/accounts/1")).andExpect(status().isOk()); + + + MockMvcResultMatchers.* defines a number of static + members, some of which return types with additional methods, for + asserting the result of the performed request. The assertions + fall in two general categories. + + The first category of assertions verify properties of the + response, i.e the response status, headers, and content. + Those are the most important things to test for. + + The second category of assertions go beyond the response, and allow + inspecting Spring MVC specific constructs such as which controller + method processed the request, whether an exception was raised and handled, + what the content of the model is, what view was selected, what flash + attributes were added, and so on. It is also possible to + verify Servlet specific constructs such as request and session attributes. + The following test asserts that binding/validation failed: + + + +mockMvc.perform(post("/persons")) + .andExpect(status().isOk()) + .andExpect(model().attributeHasErrors("person")); + + + Many times when writing tests, it's useful to dump the result + of the performed request. This can be done as follows: + + +mockMvc.perform(post("/persons")) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(model().attributeHasErrors("person")); + + + where print() is a static import from + MockMvcResultHandlers. As long as request processing + does cause an unhandled exception, the print() + method will print all the available result data to System.out. + + In some cases, you may want to get direct access to the result and + verify something that cannot be verified otherwise. This can be done by + appending .andReturn() at the end after all expectations: + + +MvcResult mvcResult = mockMvc.perform(post("/persons")).andExpect(status().isOk()).andReturn(); +// ... + + + When all tests repeat the same expectations, you can define + the common expectations once when building the + MockMvc: + + +standaloneSetup(new SimpleController()) + .alwaysExpect(status().isOk()) + .alwaysExpect(content().contentType("application/json;charset=UTF-8")) + .build() + + + Note that the expectation is always applied + and cannot be overridden without creating a separate + MockMvc instance. + + When JSON response content contains hypermedia links created with + Spring HATEOAS, + the resulting links can be verified: + + +mockMvc.perform(get("/people").accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.links[?(@.rel == 'self')].href").value("http://localhost:8080/people")); + + + When XML response content contains hypermedia links created with + Spring HATEOAS, + the resulting links can be verified: + + +Map<String, String> ns = Collections.singletonMap("ns", "http://www.w3.org/2005/Atom"); +mockMvc.perform(get("/handle").accept(MediaType.APPLICATION_XML)) + .andExpect(xpath("/person/ns:link[@rel='self']/@href", ns).string("http://localhost:8080/people")); + + +
+ +
+ Filter Registrations + + When setting up a MockMvc, you can + register one or more Filter instances: + + +mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build(); + + + Registered filters will be invoked through + MockFilterChain from + spring-test and the last filter will delegates to + the DispatcherServlet. + +
+ +
+ Further Server-Side Test Examples + + The framework's own tests include + many sample tests + intended to demonstrate how to use Spring MVC Test. + Browse these examples for further ideas. Also the + spring-mvc-showcase + has full test coverage based on Spring MVC Test. +
+ +
+ +
+ Client-Side REST Tests + + Client-side tests are for code using the RestTemplate. + The goal is to define expected requests and provide "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(); + + + In the above example, MockRestServiceServer -- + the central class for client-side REST tests -- configures the + RestTemplate with a custom + ClientHttpRequestFactory that asserts + actual requests against expectations and returns "stub" responses. + In this case we expect a single request to "/greeting" and want to return + a 200 response with "text/plain" content. We could define as many + additional requests and stub responses as necessary. + + Once expected requests and stub + responses have been defined, the RestTemplate can + be used in client-side code as usual. At the end of the tests + mockServer.verify() + can be used to verify that all expected requests were performed. + +
+ Static Imports + + Just like with server-side tests, the fluent API for client-side tests + requires a few static imports. Those are easy to find by searching + "MockRest*". Eclipse users should add + "MockRestRequestMatchers.*" and + "MockRestResponseCreators.*" + as "favorite static members" in the Eclipse preferences under + Java -> Editor -> Content Assist -> Favorites. + That allows using content assist after typing + the first character of the static method name. + Other IDEs (e.g. IntelliJ) may not require any additional + configuration. Just check the support for code completion + on static members. +
+ +
+ Further Examples of Client-side REST Tests + + Spring MVC Test's own tests include + example tests + of client-side REST tests. +
+ +
+ +
+
PetClinic Example