Spring Boot Web MVC Testing
Note: All the code here is licensed under the Apache License version 2.0.
Update: The information and in this post has been adapted back into the Getting Started Guide. I recommend reading the guide, as it is more complete and easier to follow.
For the last few weeks I've been using Spring Boot to build a REST application for an internal service. I started with the Getting Started Guide and built it up from there. Once I got the application in the guide working, I started to look at how to write tests for such an application to drive further development.
I started by forking the guide's source code sample on GitHub and creating the requisite test source directories following the usual Maven conventions.
For reference, the web endpoint in the sample application can be found in class
HelloController
:
package hello;
// imports
@RestController
public class HelloController {
@RequestMapping("/hello")
public String index() {
return "Greetings from Spring Boot!";
}
}
The application just returns a document containing a plain string when clients
request the /hello
endpoint.
Unit testing
Getting set up for testing requires a
dependency to be added to the pom.xml
as follows:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
This gives us JUnit and some other stuff useful for working with Spring applications.
For the test itself, we can hit the web endpoint defined by the
HelloController
class without spinning up a Servlet container and the rest
of the application. This allows us to mock out any dependencies not under
test (none for the example application) and verify the behaviour of the
controller.
package hello;
// imports
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@ContextConfiguration(classes = MockServletContext.class)
@WebAppConfiguration
public class HelloControllerTest {
private MockMvc mvc;
@Before
public void setUp() throws Exception {
mvc = MockMvcBuilders.standaloneSetup(
new HelloController())
.build();
}
@Test
public void getHello() throws Exception {
mvc.perform(MockMvcRequestBuilders
.get("/hello")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().string(
is("Greetings from Spring Boot!")));
}
}
The annotations on the test class orchestrate the setup of the application
context here. @SpringApplicationConfiguration
tells Spring to load the
application context from the application's normal entry point, building any
required beans etc. @WebAppConfiguration
sets up some machinery required to
enable testing of web endpoints. @ContextConfiguration
adds a @Configuration
class to the context to enable some more beans, in this case to provide a mock
Java Servlet container.
The test itself just uses Spring's testing library to call GET /hello
and
verifies that the returned value is what was expected. There are more complex
ways to assert on the result of hitting an endpoint but this is sufficient for
the sample application.
Integration testing
Maven does not enable the integration testing build phase by default so we have to add the failsafe plugin to make it work:
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.18</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
With the failsafe plugin in place, maven will run our integration test.
package hello;
// imports
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
@IntegrationTest({"server.port=9090"})
public class HelloControllerIT {
private String base;
private RestTemplate template;
@Before
public void setUp() throws Exception {
this.base = "http://localhost:9090/hello";
template = new TestRestTemplate();
}
@Test
public void getHello() throws Exception {
ResponseEntity<String> response =
template.getForEntity(base, String.class);
assertThat(response.getBody(),
is("Greetings from Spring Boot!"));
}
}
The integration test is very similar to the unit test but it has a different
runtime lifecycle. This time, the application will be run in an embedded Servlet
container with all its dependencies before the test code is called. The
@IntegrationTest
annotation tells Spring to run the full application stack.
Integration testing like this is unnecessary for this simple application but it can be useful when a black-box test of the entire application is required. I have found this method particularly useful for tests that touch a database tier, as it is possible to customise the beans in play for each integration test class, enabling database compatibility testing where more than one vendor is supported, or enabling the tests to run against a local instance of a distributed NoSQL database.
My code can be found on GitHub.