文章

Mockito使用技巧总结

Mockito使用技巧总结

🧪 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

1
reset(serviceA);

⚠️ 不推荐频繁使用 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);

💡 对于异步逻辑,可直接模拟完成的 FutureCompletableFuture


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 业务核心;
  • 测“行为”而非“实现”;
  • 每个测试仅验证一种逻辑路径;
  • 测试命名清晰表达业务意图。
本文由作者按照 CC BY 4.0 进行授权

热门标签