Spring Boot - Testing Spring Web Controller with @WebMvcTest - Example
In this section, we will learn how to test Spring Web Controller with @WebMvcTest.
1. @WebMvcTest
Instead of bootstrapping the entire application context for every test, @WebMvcTest allows us to initialize only the parts of the Application context that are necessary for our Spring MVC web layer. This allows us to focus on testing the controllers, views, and related components.
Following beans will be scanned while using @WebMvcTest:
- @Controller
- @ControllerAdvice
- @JsonComponent
- Converter/GenericConverter
- Filter
- WebMvcConfigurer
- HandlerMethodArgumentResolver
Regular @Component, @Service or @Repository beans are not scanned when using this annotation.
This approach not only speeds up the testing process but also ensures a focused and efficient testing environment.
This approach is also known as "slicing" the application context.
The annotation can be used to test a single controller by passing it as an attribute, for example @WebMvcTest(SomeController.class).
@WebMvcTest(EmployeeController.class)
public class EmployeeControllerTests {
@Autowired
private MockMvc mockMvc;
@MockBean
private EmployeeRepository employeeRepository;
@Test
public void someTest() {
// Test logic
}
}
- MockMvc allows you to prepare and send HTTP requests, also to validate HTTP responses.
- We use @MockBean in Spring Boot when we want to mock an object that is present in the Spring application context.@MockBean takes care of replacing the bean with what we want to simulate in our test.
2. Creating a spring boot application
First, open the Spring initializr https://start.spring.io
Then, Provide the Group and Artifact name. We have provided Group name com.knf.dev.demo and Artifact webmvctest-example. Here I selected the Maven project - language Java 17 - Spring Boot 3.1.5 , Spring Web, Spring Data JPA, and H2 Database.
Then, click on the Generate button. When we click on the Generate button, it starts packing the project in a .zip(webmvctest-example) file and downloads the project. Then, Extract the Zip file.
Then, import the project on your favourite IDE.
Final Project Directory:
Complete pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.knf.dev.demo</groupId>
<artifactId>webmvctest-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>webmvctest-example</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test-autoconfigure</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
spring-boot-starter-test starter will provide following libraries:
- JUnit
- Spring Test & Spring Boot Test
- AssertJ
- Hamcrest
- Mockito
- JSONassert
- JsonPath
Create Employee Entity Class
package com.knf.dev.demo.entity;
import jakarta.persistence.*;
@Entity
@Table(name = "employees")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
private String email;
public Employee() {
}
public Employee(long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
public Employee(String name, String email) {
this.name = name;
this.email = email;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
Create Employee Repository
package com.knf.dev.demo.repository;
import com.knf.dev.demo.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}
Create Employee Controller
package com.knf.dev.demo.controller;
import com.knf.dev.demo.entity.Employee;
import com.knf.dev.demo.repository.EmployeeRepository;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/api/v1")
public class EmployeeController {
private final EmployeeRepository employeeRepository;
public EmployeeController(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
@GetMapping("/employees")
public ResponseEntity<List<Employee>> getAllEmployees() {
List<Employee> employees = employeeRepository.findAll();
return new ResponseEntity<>(employees, HttpStatus.OK);
}
@GetMapping("/employees/{id}")
public ResponseEntity<Employee> getEmployeeById(@PathVariable("id") long id) {
Optional<Employee> employee = employeeRepository.findById(id);
return new ResponseEntity<>(employee.get(), HttpStatus.OK);
}
@PostMapping("/employees")
public ResponseEntity<Employee> createEmployee(@RequestBody Employee employee) {
Employee _employee = employeeRepository.
save(new Employee(employee.getName(), employee.getEmail()));
return new ResponseEntity<>(_employee, HttpStatus.CREATED);
}
@PutMapping("/employees/{id}")
public ResponseEntity<Employee> updateEmployee(@PathVariable("id") long id,
@RequestBody Employee employee) {
Optional<Employee> employeeData = employeeRepository.findById(id);
if (employeeData.isPresent()) {
Employee _employee = employeeData.get();
_employee.setName(employee.getName());
_employee.setEmail(employee.getEmail());
return new ResponseEntity<>(employeeRepository.save(_employee),
HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
@DeleteMapping("/employees/{id}")
public ResponseEntity<HttpStatus> deleteEmployee(@PathVariable("id") long id) {
employeeRepository.deleteById(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}
WebmvctestExampleApplication class
package com.knf.dev.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class WebmvctestExampleApplication {
public static void main(String[] args) {
SpringApplication.run(WebmvctestExampleApplication.class, args);
}
}
Write Unit test for EmployeeController
The EmployeeController is a @RestController and thus part of the Web Layer that we want to test.
Create EmployeeControllerTests
package com.knf.dev.demo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.knf.dev.demo.controller.EmployeeController;
import com.knf.dev.demo.entity.Employee;
import com.knf.dev.demo.repository.EmployeeRepository;
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.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
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(EmployeeController.class)
public class EmployeeControllerTests {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@MockBean
private EmployeeRepository employeeRepository;
@Test
void shouldUpdateEmployee() throws Exception {
long id = 1L;
Employee employee = new Employee(1, "Alpha", "alpha@tmail.com");
Employee updatedEmployee = new Employee(id, "Beta", "alpha@tmail.com");
when(employeeRepository.findById(id)).thenReturn(Optional.of(employee));
when(employeeRepository.save(any(Employee.class))).thenReturn(updatedEmployee);
mockMvc.perform(put("/api/v1/employees/{id}", id).
contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(updatedEmployee)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value(updatedEmployee.getName()))
.andExpect(jsonPath("$.email").value(updatedEmployee.getEmail()))
.andDo(print());
}
@Test
void shouldReturnListOfEmployees() throws Exception {
List<Employee> employees = new ArrayList<>(
Arrays.asList(new Employee(1, "Alpha", "alpha@tmail.com"),
new Employee(2, "Beta", "beta@tmail.com"),
new Employee(3, "Gama", "gama@tmail.com")));
when(employeeRepository.findAll()).thenReturn(employees);
mockMvc.perform(get("/api/v1/employees"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.size()").value(employees.size()))
.andDo(print());
}
@Test
void shouldReturnEmployee() throws Exception {
long id = 1L;
Employee employee = new Employee(1, "Alpha", "alpha@tmail.com");
when(employeeRepository.findById(id)).thenReturn(Optional.of(employee));
mockMvc.perform(get("/api/v1/employees/{id}", id)).andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(id))
.andExpect(jsonPath("$.name").value(employee.getName()))
.andExpect(jsonPath("$.email").value(employee.getEmail()))
.andDo(print());
}
@Test
void shouldCreateEmployee() throws Exception {
Employee employee = new Employee(1, "Alpha", "alpha@tmail.com");
mockMvc.perform(post("/api/v1/employees").
contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(employee)))
.andExpect(status().isCreated())
.andDo(print());
}
@Test
void shouldDeleteEmployee() throws Exception {
long id = 1L;
doNothing().when(employeeRepository).deleteById(id);
mockMvc.perform(delete("/api/v1/employees/{id}", id))
.andExpect(status().isNoContent())
.andDo(print());
}
}
- MockMvc allows you to prepare and send HTTP requests, also to validate HTTP responses.
- We use @MockBean in Spring Boot when we want to mock an object that is present in the Spring application context.@MockBean takes care of replacing the bean with what we want to simulate in our test.
- We can use MockMvc::andExpect in combination with the jsonPath method to test the structure and the content of our response.
3. Run the test
Or you can run the test using following command:
mvn test -Dtest=EmployeeControllerTests