Introduction
During this exercise we will add some MockMvc tests to test your freshly created controller(s)
Background info
MockMvc is a framework which enables you to test your controller physically and in isolation without using your Postman tool or such. And it allows you to repeat this test during every Maven build.
There are two main approaches to set up MockMvc:
-
@WebMvcTest (slice test) - loads only the web layer for a specific controller. This is the recommended approach for unit testing controllers in isolation. It is fast because it does not load the full application context.
-
@SpringBootTest + @AutoConfigureMockMvc - loads the full application context. Use this for integration tests where you want to test the full stack.
We will use the @WebMvcTest approach in this exercise.
Since Spring Boot 3.4+, @MockitoBean replaces the older @MockBean annotation. In Spring Boot 4, @MockBean is removed entirely. Use @MockitoBean instead.
|
| Spring Boot 4 also introduces RestTestClient as a unified fluent API for both MockMvc-backed and live-server tests. For this exercise we stick with MockMvc, but be aware that RestTestClient is the modern alternative. |
Code
This exercise is pretty code-based, hence below you will find some code for a Car oriented MockMvc controller test.
| Be aware of the comments in the code. They are added to prevent you from (accidentally) removing the static imports and then not knowing anymore which imports they were. |
Example code for a CarController POST test
package com.acme.carapp.api;
import com.acme.carapp.model.Car;
import com.acme.carapp.service.CarService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.hamcrest.core.Is.is;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/*
* Static imports reminder (in case your IDE removes them):
*
* import static org.hamcrest.core.Is.is;
* import static org.mockito.Mockito.any;
* import static org.mockito.Mockito.when;
* import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
* import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
* import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
* import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
*/
@WebMvcTest(CarController.class) (1)
public class CarControllerTest {
@Autowired
private MockMvc mockMvc; (2)
@MockitoBean (3)
private CarService carService;
@Test
void addCarTest() throws Exception {
Car car = new Car();
car.setBrand("Volvo");
car.setLicensePlate("AB-123-CD");
car.setMileage(45000);
Car savedCar = new Car();
savedCar.setBrand("Volvo");
savedCar.setLicensePlate("AB-123-CD");
savedCar.setMileage(45000);
// Simulate that the DB assigned an id
// (use reflection or a test constructor to set the id)
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(car);
when(this.carService.save(any(Car.class))).thenReturn(savedCar);
this.mockMvc.perform(post("/api/cars")
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andDo(print())
.andExpect(jsonPath("$.brand", is(car.getBrand())))
.andExpect(jsonPath("$.licensePlate", is(car.getLicensePlate())))
.andExpect(jsonPath("$.mileage", is(car.getMileage())))
.andExpect(status().isOk());
}
}
| 1 | @WebMvcTest loads only the web layer for the specified controller - this is a slice test |
| 2 | MockMvc is auto-configured and injected by Spring |
| 3 | @MockitoBean replaces the old @MockBean (removed in Spring Boot 4) |
Example code for CarController GET test
@Test
void findCarByIdTest() throws Exception {
Car car = new Car();
car.setBrand("BMW");
car.setLicensePlate("XY-456-ZZ");
car.setMileage(12000);
when(this.carService.findById(3L)).thenReturn(Optional.of(car));
this.mockMvc.perform(get("/api/cars/3")
.contentType(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(jsonPath("$.brand", is(car.getBrand())))
.andExpect(jsonPath("$.licensePlate", is(car.getLicensePlate())))
.andExpect(jsonPath("$.mileage", is(car.getMileage())))
.andExpect(status().isOk());
}
Example code for CarController GET (not found) test
@Test
void findCarByIdNotFoundTest() throws Exception {
when(this.carService.findById(999L)).thenReturn(Optional.empty());
this.mockMvc.perform(get("/api/cars/999")
.contentType(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isNotFound());
}
Example code for CarController DELETE test
@Test
void deleteCarByIdTest() throws Exception {
Car car = new Car();
car.setBrand("Audi");
when(this.carService.findById(1L)).thenReturn(Optional.of(car));
this.mockMvc.perform(delete("/api/cars/1")
.contentType(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isNoContent());
}
Exercise
The exercise consists of moving the code above to your frame of reference, i.e. your CarApp (or your personal hobby project).
-
Create a test class for your CarController (or your own controller)
-
Add tests for the POST, GET, PUT and DELETE endpoints
-
Run the tests using
mvn clean testor from your IDE
-
Make sure to use
@WebMvcTest(YourController.class)to keep the test fast -
Use
@MockitoBeanto mock the service layer -
Use the
ObjectMapperto convert your objects to JSON for POST and PUT requests -
Use
jsonPathexpressions to validate the response body -
Use
status().isOk(),status().isCreated(),status().isNoContent(),status().isNotFound()to validate the HTTP status codes
Further reading
MockMvc - Spring Framework Reference |
https://docs.spring.io/spring-framework/reference/testing/mockmvc.html |
Testing the Web Layer - Spring Guide |
|
Test a Spring Boot REST Controller with JUnit 5 |
https://howtodoinjava.com/spring-boot2/testing/rest-controller-unit-test-example/ |