The Master Class of "Learn Spring Security" is out:
1. Overview
Integration testing plays an important role in the application development cycle by verifying end-to-end behavior of the system.
In this article we will see how we can leverage the Spring MVC test framework in order to write and run integration tests that test controllers without explicitly starting a Servlet container.
2. Preparation
The following Maven dependencies are needed for running integration tests as described in this article. First and foremost the latest JUnit and Spring test dependencies:
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.3.2.RELEASE</version> <scope>test</scope> </dependency>
For effective asserting of results, we’re going to also use Hamcrest and JSON path:
<dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-library</artifactId> <version>1.3</version> <scope>test</scope> </dependency> <dependency> <groupId>com.jayway.jsonpath</groupId> <artifactId>json-path</artifactId> <version>2.2.0</version> <scope>test</scope> </dependency>
3. Spring MVC Test Configuration
Let’s now introduce how to configure and run the Spring enabled tests.
3.1. Enable Spring in Tests
First, any Spring enabled test will run with the help of @RunWith(SpringJUnit4ClassRunner.class); the runner is essentially the entry-point to start using the Spring Test framework.
We also need the @ContextConfiguration annotations to load the context configuration and bootstrap the context that the test will use.
Let’s have a look:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = { ApplicationConfig.class }) @WebAppConfiguration public class GreetControllerIntegrationTest { .... }
Notice how, in @ContextConfiguration, we provided the ApplicationConfig.class config class which loads the configuration we need for this particular test.
We used a Java configuration class here to specify the context configuration; similarly we can use the XML based configuration:
@ContextConfiguration(locations={""})
Finally – the test is also annotated with @WebAppConfiguration – which will load the web application context.
By default, it looks for the root web application at default path src/main/webapp; the location can be overridden by passing value argument as:
@WebAppConfiguration(value = "")
3.2. The WebApplicationContext Object
WebApplicationContext (wac) provides the web application configuration. It loads all the application beans and controllers into the context.
We’ll now be able to wire the web application context right into the test:
@Autowired private WebApplicationContext wac;
3.3. Mocking Web Context Beans
MockMvc provides support for Spring MVC testing. It encapsulates all web application beans and make them available for testing.
Let’s see how to use it:
private MockMvc mockMvc; @Before public void setup() throws Exception { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); }
We need to initialize the mockMvc object in the @Before annotated method, so that we don’t need to initialize it inside every test.
3.4. Verify Test Configuration
For our tutorial here, let’s actually verify that we’re loading the WebApplicationContext object (wac) properly. We’ll also verify that the right servletContext is being attached:
@Test public void givenWac_whenServletContext_thenItProvidesGreetController() { ServletContext servletContext = wac.getServletContext(); Assert.assertNotNull(servletContext); Assert.assertTrue(servletContext instanceof MockServletContext); Assert.assertNotNull(wac.getBean("greetController")); }
Notice that we’re also checking that we a GreetController.java bean exists in the web context – which ensures that spring beans are loaded properly.
At this point, the setup of integration test, is done. Let us see how we can test resource methods using the MockMvc object.
4. Writing Integration Tests
In this section we’ll go over the basic operation available through the test framework.
We’ll show how to send requests with path variables and parameters. Also, we’ll follow with the few examples that show how to assert that the proper view name is resolved, or that the response body is as expected.
The following snippets use static imports from MockMvcRequestBuilders or MockMvcResultMatchers classes.
4.1. Verify View Name
Let’s invoke the /homePage endpoint from our test as:
http://localhost:8080/spring-mvc-test/
or
http://localhost:8080/spring-mvc-test/homePage
Code Snippet:
@Test public void givenHomePageURI_whenMockMVC_thenReturnsIndexJSPViewName() { this.mockMvc.perform(get("/homePage")).andDo(print()) .andExpect(view().name("index")); }
Let’s break that down:
- perform() method will call a get request method which returns the ResultActions. Using this result we can have assertion expectations on response like content, HTTP status, header, etc
- andDo(print()) will print the request and response. This is helpful to get detailed view in case of error
- andExpect() will expect the provided argument. In our case we are expecting “index” to be returned via MockMvcResultMatchers.view()
4.2. Verify Response Body
We will invoke /greet endpoint from our test as:
http://localhost:8080/spring-mvc-test/greet
Expected Output:
{ "id": 1, "message": "Hello World!!!" }
Code Snippet:
@Test public void givenGreetURI_whenMockMVC_thenVerifyResponse() { MvcResult mvcResult = this.mockMvc.perform(get("/greet")) .andDo(print()).andExpect(status().isOk()) .andExpect(jsonPath("$.message").value("Hello World!!!")) .andReturn(); Assert.assertEquals("application/json;charset=UTF-8", mvcResult.getResponse().getContentType()); }
Let’s see exactly what’s going on:
- andExpect(MockMvcResultMatchers.status().isOk()) will verify that response http status is Ok i.e. 200. This ensures that request was successfully executed
- andExpect(MockMvcResultMatchers.jsonPath(“$.message”).value(“Hello World!!!”)) will verify that response content matches with the argument “Hello World!!!“. Here we used jsonPath which extracts response content and provide the requested value
- andReturn() will return the MvcResult object which is used, when we have to verify something which is not achievable by library. You can see we have added assertEquals to match the content type of response that is extracted from MvcResult object
4.3. Send GET Request with Path Variable
We will invoke /greetWithPathVariable/{name} endpoint from our test as:
http://localhost:8080/spring-mvc-test/greetWithPathVariable/John
Expected Output:
{ "id": 1, "message": "Hello World John!!!" }
Code Snippet:
@Test public void givenGreetURIWithPathVariable_whenMockMVC_thenResponseOK() { this.mockMvc .perform(get("/greetWithPathVariable/{name}", "John")) .andDo(print()).andExpect(status().isOk()) .andExpect(content().contentType("application/json;charset=UTF-8")) .andExpect(jsonPath("$.message").value("Hello World John!!!")); }
MockMvcRequestBuilders.get(“/greetWithPathVariable/{name}”, “John”) will send request as “/greetWithPathVariable/John“.
This becomes easier with respect to readability and knowing what are the parameters which are dynamically set in URL. This method doesn’t restricts to pass number of path parameters.
4.4. Send GET Request with Query Parameters
We will invoke /greetWithQueryVariable?name={name} endpoint from our test as:
http://localhost:8080/spring-mvc-test /greetWithQueryVariable?name=John%20Doe
Expected Output:
{ "id": 1, "message": "Hello World John Doe!!!" }
Code Snippet:
@Test public void givenGreetURIWithQueryParameter_whenMockMVC_thenResponseOK() { this.mockMvc.perform(get("/greetWithQueryVariable") .param("name", "John Doe")).andDo(print()).andExpect(status().isOk()) .andExpect(content().contentType("application/json;charset=UTF-8")) .andExpect(jsonPath("$.message").value("Hello World John Doe!!!")); }
param(“name”, “John Doe”) will append the query parameter in the GET request. It is similar to “/greetWithQueryVariable?name=John%20Doe“.
The query parameter can also be implemented using URI template style:
this.mockMvc.perform( get("/greetWithQueryVariable?name={name}", "John Doe"));
4.5. Send POST Request
We will invoke /greetWithPost endpoint from our test as:
http://localhost:8080/spring-mvc-test/greetWithPost
Expected Output:
{ "id": 1, "message": "Hello World!!!" }
Code Snippet:
@Test public void givenGreetURIWithPost_whenMockMVC_thenVerifyResponse() { this.mockMvc.perform(post("/greetWithPost")).andDo(print()) .andExpect(status().isOk()).andExpect(content() .contentType("application/json;charset=UTF-8")) .andExpect(jsonPath("$.message").value("Hello World!!!")); }
MockMvcRequestBuilders.post(“/greetWithPost”) will send the post request. Path variables and Query Parameters can be set in similar way we looked earlier, whereas Form Data can be set via param() method only similar to Query Parameter as:
http://localhost:8080/spring-mvc-test/greetWithPostAndFormData
Form Data:
id=1;name=John%20Doe
Expected Output:
{ "id": 1, "message": "Hello World John Doe!!!" }
Code Snippet:
@Test public void givenGreetURIWithPostAndFormData_whenMockMVC_thenResponseOK() { this.mockMvc.perform(post("/greetWithPostAndFormData").param("id", "1") .param("name", "John Doe")).andDo(print()).andExpect(status().isOk()) .andExpect(content().contentType("application/json;charset=UTF-8")) .andExpect(jsonPath("$.message").value("Hello World John Doe!!!")) .andExpect(jsonPath("$.id").value(1)); }
In above code snippet we have added 2 parameters id as “1” and name as “John Doe”.
5. Conclusion
In this quick tutorial we implemented a few simple Spring enabled integration tests.
We also looked at the WebApplicationContext and MockMVC object creation which played important role in calling the endpoints of the application.
Looking further we covered how we can send GET and POST request with variations of parameter passing and how to verify the HTTP response status, header and content.
Finally, the implementation of all these examples and code snippets is available in GitHub.