本文目录导读:

- 目录导读
- Mockito是什么?为什么必须学?
- 环境搭建:5分钟快速集成
- 核心用法:Mock、Stub与Verify
- 高级技巧:参数匹配器与Mockito Spy
- 常见问答:90%开发者遇过的坑
- 实战案例:测试一个订单服务
- SEO优化建议:搜索引擎眼中的高质量Mockito内容
- 总结与行动清单
Mockito实战指南:从入门到精通,单元测试的得力助手
目录导读
- Mockito是什么?为什么必须学?
- 环境搭建:5分钟快速集成
- 核心用法:Mock、Stub与Verify
- 高级技巧:参数匹配器与Mockito Spy
- 常见问答:90%开发者遇过的坑
- 实战案例:测试一个订单服务
- SEO优化建议:搜索引擎眼中的高质量Mockito内容
Mockito是什么?为什么必须学?
问答环节
Q: Mockito能替代真实数据库吗?
A: 不能,Mockito模拟的是对象行为,而非数据存储,它适用于测试业务逻辑,比如验证“当用户余额不足时,是否抛异常”,而不是测试数据库连接。
核心概念
Mockito是Java生态中最流行的Mock框架(GitHub 14k+ Stars),它的核心作用是:
- 隔离依赖:在单元测试中,模拟外部服务(如数据库、REST API)的返回值
- 验证行为:检查某个方法是否被调用、调用次数、调用参数
- 提升速度:无需加载Spring上下文、连接数据库即可运行测试
对比其他框架
| 框架 | 适用场景 | 学习曲线 |
|------|----------|----------|
| Mockito | 纯Java单元测试 | ★☆☆☆☆ |
| EasyMock | 较老项目 | ★★☆☆☆ |
| PowerMock | 需模拟静态方法 | ★★★☆☆ |
环境搭建:5分钟快速集成
Maven依赖(推荐)
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.12.0</version> <!-- 2025年最新稳定版 -->
<scope>test</scope>
</dependency>
Gradle依赖
testImplementation 'org.mockito:mockito-core:5.12.0'
踩坑提示:
- 如果使用JUnit 5,需额外引入
mockito-junit-jupiter - 若发现
MockitoAnnotations.openMocks(this)报错,请检查Mockito版本与Java版本兼容性(Mockito 5.x 要求 Java 11+)
核心用法:Mock、Stub与Verify
1 创建Mock对象
// 方式1:注解(推荐)
@Mock
UserService userService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
// 方式2:直接创建
UserService userService = Mockito.mock(UserService.class);
2 定义Stub(打桩)
// 当调用findUser(1)时,返回一个模拟用户
when(userService.findUser(1)).thenReturn(new User(1, "Alice"));
// 带异常的场景
when(userService.deleteUser(anyInt())).thenThrow(new RuntimeException("删除失败"));
3 Verify验证
// 验证findUser方法被调用了一次 verify(userService, times(1)).findUser(1); // 验证从未调用 verify(userService, never()).findUser(anyInt());
关键区别:
when().thenReturn()是“设定期望行为”verify()是“事后检查是否按预期执行”
高级技巧:参数匹配器与Mockito Spy
1 参数匹配器(Argument Matchers)
// 匹配任何整数 when(userService.findUser(anyInt())).thenReturn(mockUser); // 匹配特定类型的任意值 verify(userService, atLeastOnce()).findUser(argThat(id -> id > 0));
2 Mockito Spy(部分模拟)
// Spy在不覆盖时使用真实方法,Mock则全部模拟
List<String> list = new ArrayList<>();
List<String> spyList = Mockito.spy(list);
spyList.add("A");
when(spyList.size()).thenReturn(100); // 覆盖真实方法
System.out.println(spyList.get(0)); // 输出 "A"(真实行为)
System.out.println(spyList.size()); // 输出 100(Mock行为)
使用场景:
- 遗留代码中,只想Mock某几个方法,其余保持真实逻辑
- 测试时需观察内部状态变化(如缓存更新)
常见问答:90%开发者遇过的坑
Q1: Mockito无法mock静态方法怎么办?
A: Mockito 3.4.0+ 支持Mock静态方法(需添加 mockito-inline 依赖)。
try (MockedStatic<MathUtils> mocked = Mockito.mockStatic(MathUtils.class)) {
mocked.when(() -> MathUtils.add(1, 2)).thenReturn(10);
assertEquals(10, calculator.calculate());
}
Q2: 模拟的List调用add()后,验证失败?
A: 验证的是Mock对象上的方法调用,而非原List,确保使用的是同一个Mock对象。
List<String> mockList = mock(List.class);
mockList.add("A");
verify(mockList).add("A"); // 正确
Q3: 如何重置Mock状态?
reset(userService); // 清空所有stub和调用记录(慎用!破坏测试独立性)
Q4: Mockito和Spring Boot Test如何配合?
原理:
- 在集成测试中,用
@MockBean替换Spring上下文中的真实Bean - 避免加载整个ApplicationContext(可结合
@WebMvcTest切片测试)
实战案例:测试一个订单服务
待测类(OrderService)
public class OrderService {
private final PaymentClient paymentClient;
private final UserRepository userRepository;
public String createOrder(int userId, double amount) {
User user = userRepository.findById(userId);
if (user.getBalance() < amount) {
throw new IllegalArgumentException("余额不足");
}
return paymentClient.charge(userId, amount);
}
}
编写测试(关键步骤)
@ExtendWith(MockitoExtension.class) // JUnit 5集成
class OrderServiceTest {
@Mock
PaymentClient paymentClient;
@Mock
UserRepository userRepository;
@InjectMocks // 自动将mock注入到OrderService中
OrderService orderService;
@Test
void testCreateOrder_Success() {
// 准备
User user = new User(1, 500.0);
when(userRepository.findById(1)).thenReturn(user);
when(paymentClient.charge(1, 200.0)).thenReturn("PAY-001");
// 执行
String result = orderService.createOrder(1, 200.0);
// 验证
assertEquals("PAY-001", result);
verify(paymentClient, times(1)).charge(1, 200.0);
}
@Test
void testCreateOrder_InsufficientBalance() {
User user = new User(1, 100.0);
when(userRepository.findById(1)).thenReturn(user);
assertThrows(IllegalArgumentException.class,
() -> orderService.createOrder(1, 200.0)
);
verify(paymentClient, never()).charge(anyInt(), anyDouble());
}
}
测试结果:
- 第一个测试验证:余额充足时,支付接口被调用,并返回正确订单号
- 第二个测试验证:余额不足时,抛出异常,且支付接口从未被调用
SEO优化建议:搜索引擎眼中的高质量Mockito内容
1 关键词布局(自然融入)
- 主关键词:Mockito怎么用、Mockito教程、Mockito单元测试
- 长尾关键词:Mockito Verify用法、Mockito Spy与Mock区别、Mockito静态方法Mock
- 优化策略:在H2/H3标题、段落首句、代码注释中自然出现,避免堆砌
2 结构化数据(Schema.org)
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "TechArticle",
"headline": "Mockito实战指南:从入门到精通,单元测试的得力助手",
"author": "技术团队",
"datePublished": "2025-04-15",
"description": "全方位解析Mockito核心用法、高级技巧与常见坑,包含实战案例与SEO优化建议。"
}
</script>
3 内容更新频率
- Mockito版本更新:每6个月检查一次Github Release(如v5.x新增功能)
- 问答板块:根据Stack Overflow高频问题(如“Mockito静态方法时ClassNotFoundException”)持续迭代
4 外部链接策略
- 权威来源:官网(github.com/mockito/mockito)、Baeldung教程
- 避免死链:所有外链建议使用
rel="nofollow",但内部链接(如其他技术文章)使用dofollow
总结与行动清单
| 掌握程度 | 技能点 | 预计耗时 |
|---|---|---|
| 入门 | 创建Mock、定义Stub、Verify | 1小时 |
| 进阶 | 参数匹配器、Spy、Answer接口 | 2小时 |
| 实战 | 结合Spring Test、静态方法Mock | 3小时 |
立即开始:
- 在现有项目中添加Mockito依赖
- 选一个现有的Service类,写第一个Mockito测试(只需测试一个分支)
- 运行测试,体验“无需数据库、速度提升10倍”的快感
本文深入解析Mockito的完整用法,涵盖从环境搭建到高级技巧、从常见坑到SEO优化,希望成为开发者手边最实用的Mockito参考手册。