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 + @MockBeanHTTP 接口测试
Repository 层@DataJpaTest数据库操作验证
集成测试@SpringBootTest多组件协作验证

八、扩展学习

  1. 测试框架对比:JUnit 5 vs TestNG
  2. 测试覆盖率工具:Jacoco、Clover
  3. 测试驱动开发(TDD):先写测试再写代码
  4. 持续集成(CI):GitHub Actions + Test Coverage

测试原则

  • 单元测试:关注单一职责,快速失败,无需外部依赖
  • 集成测试:验证组件协作,模拟真实环境
  • 测试覆盖率:追求 80%+ 核心逻辑覆盖,避免过度测试

测试目标

  • 所有边界条件都被覆盖
  • 所有异常场景都被模拟
  • 所有业务逻辑都有验证

通过本教程,您应能全面掌握 Spring Boot 与 Mockito 的单元测试技巧,并能够编写高效、可靠的测试代码。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java导致内存泄漏的多种情况分析

    Java导致内存泄漏的多种情况分析

    本文介绍了Java中常见的内存泄漏情况,包括生命周期长的集合、未关闭的资源连接、ThreadLocal使用不当、内部类与外部类引用非静态内部类、监听器与回调注册后没有注销,推荐使用MAT和VisualVM等工具进行内存泄漏排查,感兴趣的朋友跟随小编一起看看吧
    2026-01-01
  • Java 枚举的常用技巧汇总

    Java 枚举的常用技巧汇总

    在Java中,枚举类型是一种特殊的数据类型,允许定义一组固定的常量,默认情况下,toString方法返回枚举常量的名称,本文提供了一个完整的代码示例,展示了如何在Java中通过重写枚举的toString方法来展示枚举实例的字段信息,感兴趣的朋友一起看看吧
    2025-01-01
  • Java 高并发二:多线程基础详细介绍

    Java 高并发二:多线程基础详细介绍

    本文主要介绍Java 高并发多线程的知识,这里整理详细的资料来解释线程的知识,有需要的学习高并发的朋友可以参考下
    2016-09-09
  • 详解如何实现nacos的配置的热更新

    详解如何实现nacos的配置的热更新

    这篇文章主要为大家详细介绍了如何实现nacos的配置的热更新,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-12-12
  • 史上最全Java8日期时间工具类(分享)

    史上最全Java8日期时间工具类(分享)

    这篇文章主要介绍了史上最全Java8日期时间工具类(分享),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • 手把手带你分析SpringBoot自动装配完成了Ribbon哪些核心操作

    手把手带你分析SpringBoot自动装配完成了Ribbon哪些核心操作

    这篇文章主要介绍了详解Spring Boot自动装配Ribbon哪些核心操作的哪些操作,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-08-08
  • Java中的异常处理机制try-catch详解

    Java中的异常处理机制try-catch详解

    这篇文章主要介绍了Java中的异常处理机制try-catch详解,本文给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2024-12-12
  • 基于SpringBoot实现图片上传与显示

    基于SpringBoot实现图片上传与显示

    这篇文章主要为大家详细介绍了基于SpringBoot实现图片上传与显示,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-08-08
  • SpringBoot实现HTTP调用的七种方式总结

    SpringBoot实现HTTP调用的七种方式总结

    小编在工作中,遇到一些需要调用三方接口的任务,就需要用到 HTTP 调用工具,这里,我总结了一下 实现 HTTP 调用的方式,共有 7 种(后续会继续新增),需要的朋友可以参考下
    2023-09-09
  • 分布式医疗挂号系统SpringCache与Redis为数据字典添加缓存

    分布式医疗挂号系统SpringCache与Redis为数据字典添加缓存

    这篇文章主要为大家介绍了分布式医疗挂号系统SpringCache与Redis为数据字典添加缓存,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-04-04

最新评论