Test a Spring Boot REST Controller with JUnit 5

Learn to unit test given Spring rest controller APIs using Junit 5 and mockito. This technique can be applied to spring boot as well as spring MVC applications, both.

Spring rest controller unit test example

Learn to unit test given Spring Boot REST controller using Junit 5, Mockito and MockMvc autoconfigured using @WebMvcTest. This technique can be applied to Spring boot as well as Spring MVC applications.

1. Setup

Start by including the latest version of spring-boot-starter-test starter dependency.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

Here is the Spring boot rest controller for which we will be writing unit tests.

  • The controller has a dependency on EmployeeDAO class for persistence.
  • addEmployee() api needs access to the request context using ServletUriComponentsBuilder.
  • addEmployee() api returns HTTP status and header using 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,  @ControllerAdvice). It disables the full autoconfiguration and only configures the 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, use MockMvc bean instance 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", "[email protected]"),
      new EmployeeVO(2L, "Jane", "Smith", "[email protected]")
  );

  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", "[email protected]");
  EmployeeVO saved = new EmployeeVO(100L, "firstName", "lastName", "[email protected]");

  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

To use Mockito with Spring, include the following dependency:

<!-- 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 using the Mockito APIs. The class:

  • uses @Mock annotation to created mock object for EmployeeDAO dependency.
  • uses @InjectMocks to create EmployeeController class and also inject the mocked employeeDAO instance.
  • MockitoExtension initializes mocks and handles strict stubbings. This extension is the JUnit Jupiter equivalent of our JUnit4 MockitoJUnitRunner.
  • MockHttpServletRequest and RequestContextHolder supply the request context where code under test needs it.
  • Use org.mockito.Mockito.when() and thenReturn() apis to mock the desired behavior.
  • Finally use junit 5 assertions to assert the test results with 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", "[email protected]");
        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", "[email protected]");
        Employee employee2 = new Employee(2, "Alex", "Gussin", "[email protected]");
        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

While writing a junit test for a rest controller method, we shall keep in mind that:

  • A unit test is supposed to test only a certain part of code (i.e., code written in the controller class), so we shall mock all the dependencies injected and used in the controller class.
  • If the test utilizes outer dependencies (e.g., database/network), then it is integration testing, not unit testing.
  • We should not use any web server; otherwise, it will slow down unit testing.
  • Each unit test should be independent of other tests.
  • By definition, unit tests should be fast.

5. Conclusion

In this spring boot rest controller unit testing example with Junit 5 and mockito, we learned to write tests that mock all the controller dependencies and only test the necessary part.

We also learned that we shall not use actual webserver to run the application while unit testing. The server will be needed while integration testing only.

Happy Learning !!

Sourcecode on Github

Weekly Newsletter

Stay Up-to-Date with Our Weekly Updates. Right into Your Inbox.

Comments

Subscribe
Notify of
2 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.