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

When using JUnit 4, this annotation should be used in combination with @RunWith(SpringRunner.class). But for this example  we are using JUnit 5, there’s no need to add the equivalent @ExtendWith(SpringExtension.class).
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

Download the source code - click here!

Popular posts from this blog

Learn Java 8 streams with an example - print odd/even numbers from Array and List

Java Stream API - How to convert List of objects to another List of objects using Java streams?

Registration and Login with Spring Boot + Spring Security + Thymeleaf

Java, Spring Boot Mini Project - Library Management System - Download

ReactJS, Spring Boot JWT Authentication Example

Spring Boot + Mockito simple application with 100% code coverage

Top 5 Java ORM tools - 2024

Java - Blowfish Encryption and decryption Example

Spring boot video streaming example-HTML5

Google Cloud Storage + Spring Boot - File Upload, Download, and Delete