Spring Boot MockMVC 是 Spring 官方提供的 Web 层专用测试框架,无需启动真实的 Web 服务器,即可对 Spring Boot 应用的 REST 控制器进行测试。它能模拟 GET、POST、PUT、DELETE 等 HTTP 请求,并验证 JSON 响应、状态码、响应头等核心信息。
无需启动服务器,轻量级测试 REST 接口
模拟完整 HTTP 请求 / 响应流程
支持校验状态码、JSON 字段、响应头
结合 Lombok 简化代码,告别冗余 Getter/Setter
执行速度快,专注 Web 层接口测试
Spring Boot 3.x + JDK 17 + JUnit 5 + Mockito + MockMVC + Lombok
com.example.demo
├── domain # 实体类
│ └── Employee.java
├── util # 工具类
│ └── EmployeeIdGenerator.java
├── service # 业务层
│ └── EmployeeService.java
├── controller # REST 控制器
│ └── EmployeeController.java
└── DemoApplication.java # 启动类JDK17 + Lombok + Web + 测试依赖
<?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 http://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.2.0</version>
</parent>
<groupId>com.example</groupId>
<artifactId>mockmvc-rest-lombok</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<!-- Web 核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Lombok 简化代码 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>package com.example.demo.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 员工实体类(Lombok 简化)
*/
@Data // 自动生成 Getter/Setter/toString/equals/hashCode
@NoArgsConstructor // 无参构造
@AllArgsConstructor // 全参构造
public class Employee {
private Long employeeId;
private String firstName;
private String lastName;
private int salary;
}package com.example.demo.util;
public class EmployeeIdGenerator {
private static long employeeId = 1000;
public static synchronized long value() {
return employeeId++;
}
}package com.example.demo.service;
import com.example.demo.domain.Employee;
import com.example.demo.util.EmployeeIdGenerator;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@Service
public class EmployeeService {
private final Map<Long, Employee> employeeMap = new HashMap<>();
// 查询所有
public Collection<Employee> findAll() {
return employeeMap.values();
}
// 根据ID查询
public Optional<Employee> findById(Long employeeId) {
return Optional.ofNullable(employeeMap.get(employeeId));
}
// 新增
public Employee save(Employee employee) {
long id = EmployeeIdGenerator.value();
employee.setEmployeeId(id);
employeeMap.put(id, employee);
return employee;
}
// 更新
public Optional<Employee> update(Employee employee) {
if (!employeeMap.containsKey(employee.getEmployeeId())) {
return Optional.empty();
}
employeeMap.put(employee.getEmployeeId(), employee);
return Optional.of(employee);
}
// 删除
public Optional<Employee> delete(Long employeeId) {
return Optional.ofNullable(employeeMap.remove(employeeId));
}
}package com.example.demo.controller;
import com.example.demo.domain.Employee;
import com.example.demo.service.EmployeeService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import java.net.URI;
import java.util.Collection;
@RestController
@RequestMapping("/employees")
public class EmployeeController {
private final EmployeeService employeeService;
// 构造器注入
public EmployeeController(EmployeeService employeeService) {
this.employeeService = employeeService;
}
// 查询所有
@GetMapping
public Collection<Employee> getAll() {
return employeeService.findAll();
}
// 根据ID查询
@GetMapping("/{id}")
public Employee getById(@PathVariable Long id) {
return employeeService.findById(id)
.orElseThrow(EmployeeNotFoundException::new);
}
// 新增
@PostMapping
public ResponseEntity<Employee> create(@RequestBody Employee employee) {
Employee saved = employeeService.save(employee);
URI uri = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(saved.getEmployeeId())
.toUri();
return ResponseEntity.created(uri).body(saved);
}
// 更新
@PutMapping
public Employee update(@RequestBody Employee employee) {
return employeeService.update(employee)
.orElseThrow(EmployeeNotFoundException::new);
}
// 删除
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void delete(@PathVariable Long id) {
employeeService.delete(id)
.orElseThrow(EmployeeNotFoundException::new);
}
// 自定义异常
@ResponseStatus(HttpStatus.NOT_FOUND)
static class EmployeeNotFoundException extends RuntimeException {
public EmployeeNotFoundException() {
super("员工不存在");
}
}
}package com.example.demo;
import com.example.demo.domain.Employee;
import com.example.demo.service.EmployeeService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
// 初始化数据
@Bean
CommandLineRunner initData(EmployeeService service) {
return args -> {
service.save(new Employee(null, "Rachel", "Green", 100000));
service.save(new Employee(null, "Monica", "Geller", 40000));
};
}
}package com.example.demo.controller;
import com.example.demo.domain.Employee;
import com.example.demo.service.EmployeeService;
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.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import java.util.List;
import java.util.Optional;
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.*;
@WebMvcTest(EmployeeController.class)
class EmployeeControllerTest {
@Autowired
MockMvc mockMvc;
@MockBean
EmployeeService employeeService;
@Autowired
ObjectMapper objectMapper;
// 1. 查询所有员工
@Test
void getAllEmployeesTest() throws Exception {
List<Employee> list = List.of(
new Employee(1000L, "Rachel", "Green", 100000),
new Employee(1001L, "Monica", "Geller", 40000)
);
when(employeeService.findAll()).thenReturn(list);
mockMvc.perform(get("/employees")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.length()").value(2))
.andDo(print());
}
// 2. 根据ID查询
@Test
void getEmployeeByIdTest() throws Exception {
Employee emp = new Employee(1000L, "Rachel", "Green", 100000);
when(employeeService.findById(1000L)).thenReturn(Optional.of(emp));
mockMvc.perform(get("/employees/1000"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.employeeId").value(1000))
.andExpect(jsonPath("$.firstName").value("Rachel"))
.andDo(print());
}
// 3. 员工不存在 404
@Test
void employeeNotFoundTest() throws Exception {
when(employeeService.findById(9999L)).thenReturn(Optional.empty());
mockMvc.perform(get("/employees/9999"))
.andExpect(status().isNotFound());
}
// 4. 新增员工
@Test
void createEmployeeTest() throws Exception {
Employee emp = new Employee(null, "Test", "User", 50000);
String json = objectMapper.writeValueAsString(emp);
mockMvc.perform(post("/employees")
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isCreated())
.andDo(print());
}
// 5. 删除员工
@Test
void deleteEmployeeTest() throws Exception {
when(employeeService.delete(1000L)).thenReturn(Optional.of(new Employee()));
mockMvc.perform(delete("/employees/1000"))
.andExpect(status().isNoContent());
}
}