SpringBoot整合Mockito进行单元测试实践
更新时间:2026年05月10日 10:55:18 作者:Lisonseekpan
本文介绍了Spring Boot结合JUnit5和Mockito进行单元测试的方法,涵盖环境准备、核心概念、实战案例、高级用法、覆盖率工具等内容,同时提供了常见问题解决方案和测试原则建议,帮助读者全面掌握测试技巧,编写高效可靠的测试代码
一、环境准备
1. 添加 Maven 依赖
<dependencies> <!-- Spring Boot 测试核心依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- Mockito 核心库 --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <scope>test</scope> </dependency> <!-- 用于断言的实用工具 --> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <scope>test</scope> </dependency> <!-- JSON 路径验证(用于 Controller 测试) --> <dependency> <groupId>com.jayway.jsonpath</groupId> <artifactId>json-path-assert</artifactId> <scope>test</scope> </dependency> </dependencies>
二、核心概念与工具
1.JUnit 5 注解
| 注解 | 用途 |
|---|---|
| @Test | 标记测试方法 |
| @BeforeEach | 每个测试方法执行前调用 |
| @AfterEach | 每个测试方法执行后调用 |
| @DisplayName | 为测试方法设置可读名称 |
| @ParameterizedTest | 支持参数化测试(多组输入测试同一逻辑) |
2.Mockito 注解
| 注解 | 用途 |
|---|---|
| @Mock | 创建模拟对象(非 Spring 容器管理) |
| @InjectMocks | 自动注入被测试类(配合 @Mock 使用) |
| @MockBean | 在 Spring 容器中创建模拟 Bean(适用于 @SpringBootTest) |
| @Spy | 创建部分模拟对象(真实方法调用 + 指定方法模拟) |
三、实战案例详解
案例 1:纯 Java 服务类测试(无 Spring 上下文)
1. 被测试类
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(Long id) {
return userRepository.findById(id).orElse(null);
}
}
2. 单元测试
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
public class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
@DisplayName("测试获取用户 - 用户存在")
void testGetUserById_UserExists() {
// 1. 模拟 Repository 行为
User mockUser = new User(1L, "Alice", "alice@example.com");
when(userRepository.findById(1L)).thenReturn(Optional.of(mockUser));
// 2. 执行测试方法
User result = userService.getUserById(1L);
// 3. 验证结果
assertNotNull(result);
assertEquals("Alice", result.getName());
assertEquals("alice@example.com", result.getEmail());
// 4. 验证方法调用
verify(userRepository, times(1)).findById(1L);
}
@Test
@DisplayName("测试获取用户 - 用户不存在")
void testGetUserById_UserNotExists() {
// 1. 模拟 Repository 行为
when(userRepository.findById(999L)).thenReturn(Optional.empty());
// 2. 执行测试方法
User result = userService.getUserById(999L);
// 3. 验证结果
assertNull(result);
verify(userRepository, times(1)).findById(999L);
}
}
案例 2:Spring Boot Controller 层测试(使用 MockMvc)
1. 被测试类
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = userService.getUserById(id);
return ResponseEntity.ok(user);
}
}
2. 单元测试
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
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.test.web.servlet.MockMvc;
@WebMvcTest(UserController.class)
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
@DisplayName("测试获取用户 - 用户存在")
void testGetUserById_UserExists() throws Exception {
// 1. 模拟 Service 返回值
User mockUser = new User(1L, "Alice", "alice@example.com");
when(userService.getUserById(1L)).thenReturn(mockUser);
// 2. 发起 HTTP 请求
mockMvc.perform(get("/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1L))
.andExpect(jsonPath("$.name").value("Alice"))
.andExpect(jsonPath("$.email").value("alice@example.com"))
.andDo(print());
}
@Test
@DisplayName("测试获取用户 - 用户不存在")
void testGetUserById_UserNotExists() throws Exception {
// 1. 模拟 Service 返回值
when(userService.getUserById(999L)).thenReturn(null);
// 2. 发起 HTTP 请求
mockMvc.perform(get("/users/999"))
.andExpect(status().isNotFound());
}
}
案例 3:Repository 层测试(使用内存数据库 H2)
1. 配置 H2 内存数据库
@DataJpaTest
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
@DisplayName("测试保存用户")
void testSaveUser() {
// 1. 准备测试数据
User user = new User(null, "Bob", "bob@example.com");
User savedUser = userRepository.save(user);
// 2. 验证结果
assertNotNull(savedUser.getId());
assertEquals("Bob", savedUser.getName());
assertEquals("bob@example.com", savedUser.getEmail());
// 3. 验证数据库记录
User foundUser = userRepository.findById(savedUser.getId()).orElse(null);
assertNotNull(foundUser);
assertEquals("Bob", foundUser.getName());
}
@Test
@DisplayName("测试查询用户")
void testFindByEmail() {
// 1. 准备测试数据
User user = new User(null, "Charlie", "charlie@example.com");
userRepository.save(user);
// 2. 查询验证
User result = userRepository.findByEmail("charlie@example.com");
assertNotNull(result);
assertEquals("Charlie", result.getName());
assertEquals("charlie@example.com", result.getEmail());
}
}
案例 4:Service 层测试(结合异常场景)
1. 被测试类
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User getUserById(Long id) {
return userRepository.findById(id).orElseThrow(() -> new UserNotFoundException("User not found"));
}
}
2. 单元测试
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
public class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
@DisplayName("测试获取用户 - 抛出异常")
void testGetUserById_ThrowsException() {
// 1. 模拟 Repository 行为
when(userRepository.findById(999L)).thenReturn(Optional.empty());
// 2. 执行测试并捕获异常
assertThrows(UserNotFoundException.class, () -> {
userService.getUserById(999L);
});
// 3. 验证方法调用
verify(userRepository, times(1)).findById(999L);
}
}
四、Mockito 高级用法
1.模拟任意参数
when(userService.getUserById(anyLong())).thenReturn(new User(1L, "Test User"));
2.参数捕获与验证
ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
verify(userRepository).save(userCaptor.capture());
User capturedUser = userCaptor.getValue();
assertEquals("Alice", capturedUser.getName());
3.部分模拟(Spy)
@Spy
private UserService userService;
@Test
void testSpy() {
doReturn(new User(1L, "Mock User")).when(userService).getUserById(anyLong());
User result = userService.getUserById(1L);
assertEquals("Mock User", result.getName());
}
4.BDD 风格测试
@Test
void testBddStyle() {
// Given
User mockUser = new User(1L, "Alice");
when(userService.getUserById(1L)).thenReturn(mockUser);
// When
User result = userService.getUserById(1L);
// Then
assertNotNull(result);
assertEquals("Alice", result.getName());
}
五、测试覆盖率与报告
1.Jacoco 覆盖率报告
1.1 添加依赖
<dependency> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.11</version> </dependency>
1.2 执行测试并生成报告
mvn clean test mvn jacoco:report
1.3 查看报告
报告路径:target/site/jacoco/index.html
六、常见问题与解决方案
1.问题:@Mock注解未生效
- 原因:未调用
MockitoAnnotations.openMocks(this) - 解决方案:在
@BeforeEach方法中初始化
2.问题:Spring 上下文未加载
- 原因:未使用
@SpringBootTest - 解决方案:使用
@SpringBootTest或更细粒度的测试注解(如@WebMvcTest)
3.问题:测试方法执行顺序异常
- 原因:JUnit 5 默认按字母顺序执行测试方法
- 解决方案:使用
@Order注解或@TestMethodOrder
4.问题:测试数据库污染
- 原因:测试数据未清理
- 解决方案:使用
@DirtiesContext或手动清理数据
七、总结
| 测试类型 | 推荐注解 | 适用场景 |
|---|---|---|
| 纯 Java 服务类 | @Mock + @InjectMocks | 无 Spring 上下文的单元测试 |
| Spring Controller | @WebMvcTest + @MockBean | HTTP 接口测试 |
| Repository 层 | @DataJpaTest | 数据库操作验证 |
| 集成测试 | @SpringBootTest | 多组件协作验证 |
八、扩展学习
- 测试框架对比:JUnit 5 vs TestNG
- 测试覆盖率工具:Jacoco、Clover
- 测试驱动开发(TDD):先写测试再写代码
- 持续集成(CI):GitHub Actions + Test Coverage
测试原则:
- 单元测试:关注单一职责,快速失败,无需外部依赖
- 集成测试:验证组件协作,模拟真实环境
- 测试覆盖率:追求 80%+ 核心逻辑覆盖,避免过度测试
测试目标:
- 所有边界条件都被覆盖
- 所有异常场景都被模拟
- 所有业务逻辑都有验证
通过本教程,您应能全面掌握 Spring Boot 与 Mockito 的单元测试技巧,并能够编写高效、可靠的测试代码。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
相关文章
Spring MVC 中获取session的几种方法(小结)
这篇文章主要介绍了Spring MVC 中获取session的几种方法(小结),具有一定的参考价值,感兴趣的小伙伴们可以参考一下2017-09-09
Java读取Excel、docx、pdf和txt等文件万能方法举例
在Java开发中处理文件是常见需求,本文以实际代码示例详述如何使用ApachePOI库及其他工具读取和写入Excel、Word、PDF等文件,介绍了ApachePOI、ApachePDFBox和EasyExcel等库的使用方法,帮助开发者有效读取不同格式文件,需要的朋友可以参考下2024-09-09


最新评论