Introduction
In the previous post, we looked at creating a REST controller in Spring and how to split the business logic from the response and request logic by using services.
Now that we have written the code, let's see how to test it.
Testing a REST endpoint with Spring
When writing tests, we can consider unit tests, which are responsible for testing a single unit, typically comprising a certain business logic or we can look at integration tests, that are different from the former, in the sense that they are used to test a more complex flow that is comprised of many units of code and usually entails some sort of "application usage" logic.
For testing an endpoint, it's useful to write an integration test that can test the whole flow of the endpoint usage, namely, controlling which request parameters are sent with the request, tokens, authorization credentials and any extra needed information, and, for a given set of request parameters, we can then assert if the response is what we are expecting. We can do this by checking the contents of the response or the status code (if we expect a request to succeed then, we should have the 200 response code, for example).
Let us recap the @RestController
we had written previously:
@RestController
public class GreetingsResource {
private static final Log LOG = LogFactory.getLog(GreetingsResource.class);
//1
@PostMapping(value = "/greet", consumes = MediaType.APPLICATION_JSON_VALUE)
//2
public ResponseEntity<GreetDTO> greet(@RequestBody PersonDTO person) {
if(person==null) {
LOG.error("Request Body can not be null");
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
GreetingService greetingService = new GreetingService(person);
GreetDTO response = greetingService.getGreeting();
if(response==null){
return new ResponseEntity<>(HttpStatus.EXPECTATION_FAILED);
}
return new ResponseEntity<>(response, HttpStatus.OK);
}
}
Let's see how we can leverage this current implementation to write a good suite of integration tests for it.
Introducing the MockMvc Spring class
Spring has a lot of useful functionalities to support testing the applications written with it, which is one of its greatest features. We will only look at a fraction of what we can do with the framework, by looking at the MockMvc
class.
MockMvc
has two methods we can use:
getDispatcherServlet()
: returns the underlyingDispatcherServlet
instance that thisMockMvc
was initialized with.
This is intended for use in custom request processing scenario where a request handling component happens to delegate to theDispatcherServlet
at runtime and therefore needs to be injected with it.perform(RequestBuilder requestBuilder)
: perform a request and return a type that allows chaining further actions, such as asserting expectations, on the result.
The most useful one when writing tests in practice is the perform
method, that allows us to build a request and assert on its expected response in a very clear way thanks to the builder, and it's the method that is most commonly used.
The idea is that we can build a request just as we want or expect it to happen in a real-world scenario, and we can pass this built request to the perform
method to assert useful things on it.
Preparing a test class to support MockMvc
As SpringBoot's motto is convention over configuration, there is very little harness code that we need to configure our test class to support MockMvc:
@AutoConfigureMockMvc
@SpringBootTest
class GreetingsControllerTest {
@Autowired
private MockMvc mockMvc;
(...)
Can't be easier than this!
We simply use the @AutoConfigureMockMvc
annotation to configure the autowired MockMvc we will be using. This annotation adds useful defaults to the autowired instance we will use for the tests, like, registering application filters from the application context with our instance and enabling Selenium webDrivers if needed, amongst other things. With this scaffolding in place, we can finally write our integration tests.
Using MockMvc to write integration tests
We can use this configuration to write our integration tests. Note how we test for two distinct scenarios: a success one, following the happy path and a failure one, where the request body is empty, and we will get a no content status:
@AutoConfigureMockMvc
@SpringBootTest
class GreetingsControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void failsIfRequestBodyEmpty() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/greet")
.contentType(MediaType.APPLICATION_JSON)
.content()
.andExpect(status().is(204))); //no content status
@Test
public void successIfGreetInRequestBody() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/greet")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"name\": \"John\", \"age\":15}")
.andExpect(status().is(200))); //OK
As we can see, we can declare our requests in a straightforward way and thanks to the MockMvcRequestBuilder
it is really easy to read the test and see what's happening and what are our expectations for a specific request body, just like it would be in a real usage case.
We specify the URL for the endpoint, the content type, contents in the request body and then we can say what we expect and assert on the response status for the request.
We can do much more with MockMvc
which will be covered later in a future post.
Conclusion
Using Spring's capabilities and leveraging MockMvc, we can see how to write declarative tests that are very close to a real-world usage scenario, so they provide the most benefit.
Top comments (1)
I'm glad you found it useful! :)