🧪 Mockito 4.5 + JUnit4 单元测试实战手册
🌱 基础使用与注解说明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| @RunWith(MockitoJUnitRunner.class)
public class MyServiceTest {
@Mock
private DependencyService dependencyService;
@InjectMocks
private MyService myService;
@Test
public void testBasicMock() {
when(dependencyService.call()).thenReturn("mocked");
assertEquals("mocked", myService.doSomething());
}
}
|
| 注解 |
说明 |
@Mock |
创建并注入一个模拟对象(不会调用真实逻辑) |
@Spy |
创建真实对象的代理,可选择性 Mock 部分方法 |
@InjectMocks |
将标注了 @Mock 的对象自动注入到待测试对象 |
@Captor |
创建参数捕获器(配合 verify() 使用) |
🧩 Mock 常见模式
✅ 基本返回值
1
| when(userDao.findNameById(1)).thenReturn("Alice");
|
🚀 链式返回不同值
1
2
3
4
| when(service.getNext())
.thenReturn("A")
.thenReturn("B")
.thenThrow(new IllegalStateException("No more data"));
|
🔁 动态回答 (Answer)
1
2
3
4
| when(service.process(anyString())).thenAnswer(invocation -> {
String arg = invocation.getArgument(0);
return "processed:" + arg;
});
|
💡 用途:根据参数动态生成返回值,适合复杂逻辑。
⚠️ 模拟异常场景
1. 普通方法抛异常
1
| when(dependencyService.call()).thenThrow(new RuntimeException("call failed"));
|
2. Void 方法抛异常
1
| doThrow(new IllegalStateException("delete error")).when(service).delete(anyInt());
|
3. 自定义条件触发异常
1
2
3
4
5
| when(service.getData(anyInt())).thenAnswer(invocation -> {
int id = invocation.getArgument(0);
if (id < 0) throw new IllegalArgumentException("invalid id");
return "OK";
});
|
4. 异常断言
1
2
3
4
5
| @Test(expected = IllegalStateException.class)
public void testException() {
when(service.run()).thenThrow(new IllegalStateException());
service.run();
}
|
✅ 适用于 JUnit4;若迁移 JUnit5,可使用 assertThrows()。
🔍 验证交互 (verify)
1
2
3
4
| verify(service, times(2)).run();
verify(service, never()).delete(any());
verify(service, atLeastOnce()).save(any());
verifyNoMoreInteractions(service);
|
🔎 建议:
- 验证“结果”而非“实现细节”;
- 对无意义的多次
verify 应谨慎使用。
🎯 参数捕获与匹配器
参数匹配器
1
2
| when(repo.find(eq("Tom"), anyInt())).thenReturn("found");
verify(repo).find(startsWith("T"), anyInt());
|
捕获参数
1
2
3
4
5
6
7
8
9
| @Captor
ArgumentCaptor<String> captor;
@Test
public void testCaptor() {
service.send("hello");
verify(dependency).save(captor.capture());
assertEquals("hello", captor.getValue());
}
|
💡 可捕获多次调用参数:captor.getAllValues()。
🧠 部分 Mock 与 Spy
部分 Mock(Spy)
1
2
3
4
5
6
| List<String> list = new ArrayList<>();
List<String> spyList = spy(list);
doReturn(100).when(spyList).size();
spyList.add("A");
assertEquals(100, spyList.size());
|
⚠️ 注意:
spy() 默认执行真实方法;
- 用
doReturn() 替代 when() 避免触发真实调用。
🧰 静态方法 Mock
1
2
3
4
| try (MockedStatic<Utils> mocked = mockStatic(Utils.class)) {
mocked.when(() -> Utils.calc(anyInt())).thenReturn(42);
assertEquals(42, Utils.calc(5));
}
|
✅ 建议使用 try-with-resources 自动关闭。
✅ Mockito 4.5+ 原生支持静态 Mock(不再需要 PowerMock)。
🔄 顺序验证与交互控制
验证调用顺序
1
2
3
| InOrder inOrder = inOrder(serviceA, serviceB);
inOrder.verify(serviceA).start();
inOrder.verify(serviceB).execute();
|
验证无额外交互
1
| verifyNoMoreInteractions(serviceA, serviceB);
|
重置 Mock
⚠️ 不推荐频繁使用 reset(),容易掩盖测试问题。
🧩 高级技巧与最佳实践
1. 模拟时间依赖
1
2
| @Mock Clock clock;
when(clock.instant()).thenReturn(Instant.parse("2025-01-01T00:00:00Z"));
|
避免直接使用 System.currentTimeMillis()。
2. 异步代码测试
1
2
| CompletableFuture<String> future = CompletableFuture.completedFuture("done");
when(service.asyncCall()).thenReturn(future);
|
💡 对于异步逻辑,可直接模拟完成的 Future 或 CompletableFuture。
3. BDD 风格 (Given/When/Then)
1
2
3
4
5
6
7
8
| // given
given(repo.findUser("Tom")).willReturn(new User("Tom"));
// when
User result = service.login("Tom");
// then
then(repo).should().findUser("Tom");
|
Mockito 4.5 fully supports BDD syntax (given / then)。
4. 模拟链式调用
1
2
3
| when(chain.first()).thenReturn(chain);
when(chain.second()).thenReturn(chain);
when(chain.getResult()).thenReturn("ok");
|
5. 测试异常 + 验证交互
1
2
3
4
5
6
7
8
| @Test
public void testErrorThenRollback() {
doThrow(new RuntimeException("fail")).when(repo).save(any());
try {
service.handle();
} catch (RuntimeException ignored) {}
verify(repo).rollback();
}
|
⚡ 常见问题与陷阱
| 问题 |
原因 |
解决方案 |
InvalidUseOfMatchersException |
同时使用匹配器与实际值 |
所有参数都使用匹配器或都用实际值 |
NullPointerException on @Mock |
忘记使用 MockitoJUnitRunner |
添加 @RunWith(MockitoJUnitRunner.class) |
| 静态方法无法 Mock |
Mockito 版本过低 |
升级至 4.0+ 并启用 mockStatic() |
| Spy 调用真实方法 |
使用 doReturn() 替代 when() |
|
| 多次 Mock 无效 |
Mock 被重新初始化 |
检查是否在 @Before 重复 new 对象 |
🧾 推荐依赖与版本
1
2
| testImplementation 'org.mockito:mockito-core:4.5.1'
testImplementation 'junit:junit:4.13.2'
|
可选增强:
1
2
| testImplementation 'org.mockito:mockito-inline:4.5.1' // 支持静态 Mock
testImplementation 'org.mockito:mockito-junit-jupiter:4.5.1' // 若迁移 JUnit5
|
🏁 结语:测试哲学
“好的单元测试不只是验证结果,更是让代码结构可测试。”
关键原则:
- Mock 外部依赖,不 Mock 业务核心;
- 测“行为”而非“实现”;
- 每个测试仅验证一种逻辑路径;
- 测试命名清晰表达业务意图。