JUnit 5 + Mockito 终极实战笔记

JUnit 5 + Mockito 终极实战笔记

这份笔记涵盖了从环境搭建、核心概念辨析到复杂场景(Spy、Final类)处理的完整流程。

第一部分:JUnit 4 vs JUnit 5

在 Spring Boot 2.2+ 版本中,JUnit 5 是默认标准。

核心差异速查表

特性 JUnit 4 (老旧) JUnit 5 (现代/默认)
架构 单体 Jar 包 模块化 (Platform + Jupiter)
Test 注解 @Test (org.junit.Test) @Test (org.junit.jupiter.api.Test)
生命周期 @Before, @After @BeforeEach, @AfterEach
Spring 集成 必须写 @RunWith(SpringRunner.class) 自动集成,只需 @SpringBootTest
断言 Assert.assertEquals Assertions.assertEquals

环境搭建 (pom.xml)

Spring Boot 的 spring-boot-starter-test 默认已经包含了 JUnit 5 和 Mockito,无需额外配置。

xml 复制代码
​
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

第二部分:核心概念与优雅测试

Mock 是什么?

Mock 是一个"替身"。它代替真实的数据库或外部服务,让测试只关注当前类的逻辑,而不受外部环境影响(隔离性),且运行速度极快(无需 IO)。

优雅测试的三种策略

  1. 纯单元测试 (推荐 Service 层)

    • 特点:不启动 Spring 容器,速度最快。
    • 注解:@ExtendWith(MockitoExtension.class)
  2. 切片测试 (推荐 Controller 层)

    • 特点:只启动 Web 层容器。
    • 注解:@WebMvcTest
  3. 集成测试 (不推荐频繁使用)

    • 特点:启动完整容器,速度慢。
    • 注解:@SpringBootTest

第三部分:代码实战汇总

假设我们有两个类:

  • UserRepository (依赖,通常是数据库操作)
  • UserService (被测类,包含业务逻辑)

1. 纯单元测试 (使用 @Mock 和 @InjectMocks)

这是最标准、最优雅的写法。

java 复制代码
​
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
​
import java.util.Optional;
​
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.*;
​
// 1. 启用 Mockito 扩展,无需启动 Spring 容器
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
​
    // 2. 创建一个假的 UserRepository
    @Mock
    private UserRepository userRepository;
​
    // 3. 创建 UserService,并自动把上面的 userRepository 注入进去
    @InjectMocks
    private UserService userService;
​
    @Test
    void shouldFindUserById() {
        // --- Arrange (准备) ---
        // 定义 Mock 行为:当调用 findById(1L) 时,返回一个包含用户的 Optional
        User mockUser = new User(1L, "Alice");
        when(userRepository.findById(1L)).thenReturn(Optional.of(mockUser));
​
        // --- Act (执行) ---
        User result = userService.findById(1L);
​
        // --- Assert (断言) ---
        assertNotNull(result);
        assertEquals("Alice", result.getName());
​
        // --- Verify (验证) ---
        // 验证 findById 方法是否真的被调用了一次
        verify(userRepository, times(1)).findById(1L);
    }
}

2. 切片测试 (使用 @MockBean)

用于测试 Controller,需要 Spring 容器支持。

java 复制代码
​
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;
​
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
​
// 1. 只启动 Web 层
@WebMvcTest(UserController.class)
class UserControllerTest {
​
    @Autowired
    private MockMvc mockMvc;
​
    // 2. @MockBean:替换 Spring 容器中的 UserService 为 Mock 对象
    @MockBean
    private UserService userService;
​
    @Test
    void shouldReturnUser() throws Exception {
        // 模拟 Service 层返回数据
        when(userService.findById(1L)).thenReturn(new User(1L, "Alice"));
​
        // 发送 HTTP 请求并验证
        mockMvc.perform(get("/users/1"))
               .andExpect(status().isOk());
    }
}

第四部分:Spy (部分模拟)

当你需要测试真实对象,但想拦截其中某个方法时使用。

关键点

  • 必须手动初始化对象 (= new UserService())。
  • 存根语法必须用 doReturn(...).when(spy)。
scss 复制代码
​
@ExtendWith(MockitoExtension.class)
class UserServiceSpyTest {
​
    // 1. 必须手动 new 出来!
    @Spy
    private UserService userService = new UserService();
​
    @Mock
    private UserRepository userRepository;
​
    @Test
    void testSpy() {
        // 2. 注入依赖(Spy 对象通常不会自动注入 Mock,需手动处理或构造)
        // 这里假设我们通过构造器注入了 mock
        userService = new UserService(userRepository); 
        // 重新 Spy 包装(注意:实际开发中建议直接在构造时注入)
        
        // 3. 拦截特定方法
        doReturn("VIP").when(userService).getUserLevel(anyLong());
​
        // 4. 调用真实方法,但 getUserLevel 会返回 "VIP"
        String level = userService.getUserLevel(1L);
        
        assertEquals("VIP", level);
    }
}

第五部分:Final 类 Mock 解决方案

默认 Mockito 无法 Mock final 类。

步骤 1:添加依赖

在 pom.xml 中添加 mockito-inline。

xml 复制代码
​
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <scope>test</scope>
</dependency>

步骤 2:创建配置文件

在 src/test/resources 下创建文件夹 mockito-extensions,并创建文件 org.mockito.plugins.MockMaker。

文件内容:

ini 复制代码
​
mock-maker=inline

步骤 3:直接使用

配置好后,final 类可以直接使用 @Mock 注解,无需任何代码变更。

总结速查

  • @Mock:假的,完全隔离,用于依赖。
  • @InjectMocks:被测对象,自动注入 @Mock。
  • @Spy:真的,但可拦截,用于部分测试。
  • @MockBean:Spring 容器里的假 Bean,用于切片测试。
  • Final 类:加 mockito-inline 依赖和配置文件。

你可以直接鼠标拖拽全选 以上所有内容,按 Ctrl+C 就能一键复制啦。

(注:文档部分内容可能由 AI 生成)

相关推荐
苏三说技术2 小时前
AI中四种向量数据库
后端
江沉晚呤时3 小时前
.NET 9 快速上手 RabbitMQ 直连交换机:高效消息传递实战指南
开发语言·分布式·后端·rabbitmq·.net·ruby
编码忘我3 小时前
java单机滑动窗口限流器
后端
编码忘我4 小时前
redis的数据类型及java调用案例
后端
编码忘我4 小时前
redis分布式锁
后端
程序员爱钓鱼4 小时前
Go输出与格式化核心库:fmt包完整指南
后端·面试·go
lars_lhuan4 小时前
Go map 与并发
后端·golang
Lewiis4 小时前
Go语言的错误处理机制
开发语言·后端·golang
Gopher_HBo4 小时前
Go并发原子操作 waitGroup 对象池
后端