A unit test for a Spring Boot REST controller checks only the code of the controller class, with all its dependencies replaced by mocks. We write such a test with JUnit 5, Mockito and MockMvc, which @WebMvcTest autoconfigures. The same technique applies to Spring Boot and Spring MVC applications.
1. Setup
First, we include the latest version of the spring-boot-starter-test starter dependency.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
We write the unit tests for a Spring Boot REST controller with three details that affect the tests.
- The controller depends on the EmployeeDAO class for persistence.
- The method addEmployee() needs access to the request context through ServletUriComponentsBuilder.
- The method addEmployee() returns the HTTP status and header through the ResponseEntity class.
@RestController
@RequestMapping(path = "/employees")
public class EmployeeController
{
@Autowired
private EmployeeDAO employeeDao;
@GetMapping(path="/", produces = "application/json")
public Employees getEmployees() {
return employeeDao.getAllEmployees();
}
@PostMapping(path= "/", consumes = "application/json", produces = "application/json")
public ResponseEntity<Object> addEmployee(@RequestBody Employee employee) {
employeeDao.addEmployee(employee);
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(employee.getId())
.toUri();
return ResponseEntity.created(location).build();
}
}
2. Unit Testing using Spring Boot @WebMvcTest
The @WebMvcTest annotation is used to unit test the Spring MVC components (@Controller and @ControllerAdvice). It disables the full auto-configuration and configures only Spring Security and MockMvc.
@WebMvcTest(EmployeeRESTController.class)
public class TestEmployeeRESTController {
@Autowired
private MockMvc mvc;
@MockBean
private EmployeeService employeeService; // Mock the service used by the controller
}
Finally, we use the MockMvc bean to invoke the APIs and verify the results.
@Test
public void getAllEmployeesAPI() throws Exception {
// Mock service response
List<EmployeeVO> mockEmployees = List.of(
new EmployeeVO(1L, "John", "Doe", "john.doe@mail.com"),
new EmployeeVO(2L, "Jane", "Smith", "jane.smith@mail.com")
);
Mockito.when(employeeService.getAllEmployees()).thenReturn(mockEmployees);
mvc.perform(MockMvcRequestBuilders
.get("/employees")
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$[0].employeeId").value(1L))
.andExpect(MockMvcResultMatchers.jsonPath("$[1].employeeId").value(2L));
}
@Test
public void createEmployeeAPI() throws Exception {
EmployeeVO input = new EmployeeVO(null, "firstName", "lastName", "admin@mail.com");
EmployeeVO saved = new EmployeeVO(100L, "firstName", "lastName", "admin@mail.com");
Mockito.when(employeeService.createEmployee(Mockito.any(EmployeeVO.class))).thenReturn(saved);
mvc.perform(MockMvcRequestBuilders
.post("/employees")
.content(asJsonString(input))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isCreated())
.andExpect(MockMvcResultMatchers.jsonPath("$.employeeId").value(100L));
}
public static String asJsonString(final Object obj) {
try {
return new ObjectMapper().writeValueAsString(obj);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
3. Unit Testing Spring Controller using Mockito
We add the following dependency to use Mockito with Spring.
<!-- Mockito extention -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
The test class contains unit tests for the Spring Boot REST controller, written with the Mockito APIs. It works as follows.
- The @Mock annotation creates a mock object for the EmployeeDAO dependency.
- The @InjectMocks annotation creates the EmployeeController instance and injects the mocked employeeDAO instance.
- The extension MockitoExtension initializes the mocks and handles strict stubbings. It is the JUnit Jupiter equivalent of the JUnit 4 MockitoJUnitRunner.
- The classes MockHttpServletRequest and RequestContextHolder supply the request context where the code under test needs it.
- The methods org.mockito.Mockito.when() and thenReturn() mock the desired behavior.
- Finally, the JUnit 5 assertions compare the test results with the expected results.
Read More : Mockito annotations – @Mock, @Spy, @Captor, @InjectMocks
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import com.howtodoinjava.rest.controller.EmployeeController;
import com.howtodoinjava.rest.dao.EmployeeRepository;
import com.howtodoinjava.rest.model.Employee;
import com.howtodoinjava.rest.model.Employees;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@ExtendWith(MockitoExtension.class)
public class EmployeeControllerTest
{
@InjectMocks
EmployeeController employeeController;
@Mock
EmployeeDAO employeeDAO;
@Test
public void testAddEmployee()
{
MockHttpServletRequest request = new MockHttpServletRequest();
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
when(employeeDAO.addEmployee(any(Employee.class))).thenReturn(true);
Employee employee = new Employee(1, "Lokesh", "Gupta", "howtodoinjava@gmail.com");
ResponseEntity<Object> responseEntity = employeeController.addEmployee(employeeToAdd);
assertThat(responseEntity.getStatusCodeValue()).isEqualTo(201);
assertThat(responseEntity.getHeaders().getLocation().getPath()).isEqualTo("/1");
}
@Test
public void testFindAll() {
Employee employee1 = new Employee(1, "Lokesh", "Gupta", "howtodoinjava@gmail.com");
Employee employee2 = new Employee(2, "Alex", "Gussin", "example@gmail.com");
Employees employees = new Employees();
employees.setEmployeeList(Arrays.asList(employee1, employee2));
when(employeeDAO.getAllEmployees()).thenReturn(employees);
Employees result = employeeController.getEmployees();
assertThat(result.getEmployeeList().size()).isEqualTo(2);
assertThat(result.getEmployeeList().get(0).getFirstName()).isEqualTo(employee1.getFirstName());
assertThat(result.getEmployeeList().get(1).getFirstName()).isEqualTo(employee2.getFirstName());
}
}
4. Best Practices
When we write a JUnit test for a REST controller method, we follow these practices.
- A unit test tests only the code of the controller class, so we mock all the dependencies that the controller class injects and uses.
- If the test uses outer dependencies, such as a database or a network, it is an integration test, not a unit test.
- A web server slows down unit tests, so we do not start one.
- Each unit test is independent of the other tests.
- Unit tests are fast by definition.
5. Conclusion
In this Spring Boot REST controller unit testing example with JUnit 5 and Mockito, we wrote tests that mock all the controller dependencies and test only the controller code.
We also saw that unit tests do not use an actual web server. Only integration tests need the server.
Happy Learning !!
with this test how can it work when you use MvcMock without Mocking Repository
@Test
public void getAllEmployeesAPI() throws Exception
{
mvc.perform(MockMvcRequestBuilders
.get(“/employees”)
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath(“$.employees”).exists())
.andExpect(MockMvcResultMatchers.jsonPath(“$.employees[*].employeeId”).isNotEmpty());
}
You are absolutely right. Either service or dao must be mocked.