阅读说明:
本文为原创文章,转发请注明出处。如果觉得文章不错,请点赞、收藏、关注一下,您的认可是我写作的动力。
简介
写单元测试时,你是否也受够了数据库连接、第三方接口这些外部依赖的困扰?明明只想验证核心业务逻辑,却不得不花大量时间处理各种无关的依赖------就像想喝杯咖啡,却得先种咖啡豆一样麻烦。
这就要请出Mockito了。Mockito 是 Java 中最流行的 mocking 框架之一,它可以帮助你创建和配置 mock 对象,从而更轻松地编写单元测试。本教程将介绍 Mockito 的核心功能,包括 mock、spy、when、return、answer、matcher 和 verify 的用法。
1. 环境准备
首先,确保你的项目中已经添加了 Mockito 依赖。对于 Maven 项目:
xml
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.3.1</version> <!-- 使用最新版本 -->
<scope>test</scope>
</dependency>
对于 JUnit 5 用户,还可以添加 Mockito 扩展:
xml
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.3.1</version>
<scope>test</scope>
</dependency>
2. 基本 Mock 用法
创建 Mock 对象
arduino
import static org.mockito.Mockito.*;
// 创建一个 List 的 mock 对象
List<String> mockedList = mock(List.class);
配置 Mock 行为 (when-thenReturn)
less
// 配置 mock 对象的行为
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenReturn("second");
// 使用 mock 对象
System.out.println(mockedList.get(0)); // 输出 "first"
System.out.println(mockedList.get(1)); // 输出 "second"
System.out.println(mockedList.get(2)); // 输出 null,因为我们没有为索引 2 配置行为
验证交互 (verify)
scss
// 验证 get(0) 被调用了一次
verify(mockedList).get(0);
// 验证 get(1) 被调用了至少一次
verify(mockedList, atLeastOnce()).get(1);
// 验证 get(2) 从未被调用
verify(mockedList, never()).get(2);
3. Spy 用法
Spy 是对真实对象的包装,默认会调用真实方法,但你可以选择性地 stub 某些方法。
scss
List<String> realList = new ArrayList<>();
List<String> spiedList = spy(realList);
// 可以调用真实方法
spiedList.add("one");
spiedList.add("two");
// 验证真实行为
assertEquals(2, spiedList.size());
// 可以 stub 特定方法
when(spiedList.size()).thenReturn(100);
// 现在 size() 方法被 stubbed 了
assertEquals(100, spiedList.size());
// 其他方法仍然调用真实实现
assertEquals("one", spiedList.get(0));
4. 高级 Stubbing
thenAnswer 用法
当需要根据输入动态决定返回值时,可以使用 thenAnswer:
vbnet
Map<String, String> mockMap = mock(Map.class);
when(mockMap.get(anyString())).thenAnswer(invocation -> {
String key = invocation.getArgument(0);
return "value_for_" + key;
});
assertEquals("value_for_foo", mockMap.get("foo"));
assertEquals("value_for_bar", mockMap.get("bar"));
抛出异常
less
when(mockedList.get(anyInt())).thenThrow(new RuntimeException("Boom!"));
assertThrows(RuntimeException.class, () -> mockedList.get(0));
连续 Stubbing
less
when(mockedList.size())
.thenReturn(1)
.thenReturn(2)
.thenThrow(new RuntimeException("Too many calls"));
assertEquals(1, mockedList.size());
assertEquals(2, mockedList.size());
assertThrows(RuntimeException.class, () -> mockedList.size());
5. 参数匹配器 (Argument Matchers)
Mockito 提供了多种参数匹配器,使验证更加灵活:
less
// 使用 anyXxx 系列匹配器
when(mockedList.get(anyInt())).thenReturn("element");
// 使用 eq 匹配特定值
when(mockedList.contains(eq("specific"))).thenReturn(true);
// 使用自定义匹配器
when(mockedList.add(argThat(s -> s.length() > 5))).thenReturn(true);
// 验证
mockedList.get(999); // 任何整数都可以
mockedList.contains("specific");
mockedList.add("long string");
verify(mockedList).get(anyInt());
verify(mockedList).contains(eq("specific"));
verify(mockedList).add(argThat(s -> s.length() > 5));
常用匹配器包括:
any()
,any(Class<T>)
anyInt()
,anyString()
,anyList()
, 等等eq(value)
- 匹配特定值isNull()
,isNotNull()
argThat(matcher)
- 自定义匹配
6. 验证详细用法(verify)
验证调用次数
scss
mockedList.add("once");
mockedList.add("twice");
mockedList.add("twice");
mockedList.add("three times");
mockedList.add("three times");
mockedList.add("three times");
// 验证默认调用一次 (times(1) 是默认的)
verify(mockedList).add("once");
// 验证确切调用次数
verify(mockedList, times(2)).add("twice");
verify(mockedList, times(3)).add("three times");
// 验证从未调用
verify(mockedList, never()).add("never happened");
// 验证至少/至多调用次数
verify(mockedList, atLeastOnce()).add("three times");
verify(mockedList, atLeast(2)).add("three times");
verify(mockedList, atMost(5)).add("three times");
验证调用顺序
ini
List firstMock = mock(List.class);
List secondMock = mock(List.class);
firstMock.add("first");
secondMock.add("second");
// 创建 InOrder 对象并验证调用顺序
InOrder inOrder = inOrder(firstMock, secondMock);
inOrder.verify(firstMock).add("first");
inOrder.verify(secondMock).add("second");
验证调用参数
Mockito不仅能够验证调用次数,还能够对参数进行验证。 ArgumentCaptor
是一个强大的工具,它允许你在验证方法调用时捕获并检查传递的参数。这在以下场景特别有用:
- 需要验证传递给方法的复杂对象
- 需要检查方法调用时参数的内部状态
- 需要断言多次调用中的不同参数值
scss
@Test
void testMultipleArguments() {
Map<String, Integer> mockedMap = mock(Map.class);
mockedMap.put("key1", 100);
mockedMap.put("key2", 200);
ArgumentCaptor<String> keyCaptor = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<Integer> valueCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mockedMap, times(2)).put(keyCaptor.capture(), valueCaptor.capture());
List<String> keys = keyCaptor.getAllValues();
List<Integer> values = valueCaptor.getAllValues();
assertEquals("key1", keys.get(0));
assertEquals(100, (int) values.get(0));
assertEquals("key2", keys.get(1));
assertEquals(200, (int) values.get(1));
}
ArgumentCaptor不仅能够捕获基本类型,还支持自定义对象。
验证无更多交互
scss
mockedList.add("one");
mockedList.clear();
// 验证所有交互
verify(mockedList).add("one");
verify(mockedList).clear();
// 验证没有其他交互
verifyNoMoreInteractions(mockedList);
7. 完整示例
下面是一个完整的测试类示例,展示了 Mockito 的各种用法:
less
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
class MockitoCompleteExampleTest {
@Test
void testMockitoFeatures() {
// 1. 创建 mock 对象
List<String> mockedList = mock(List.class);
// 2. 使用 when-thenReturn 配置行为
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenReturn("second");
when(mockedList.size()).thenReturn(10);
// 3. 使用 mock 对象
assertEquals("first", mockedList.get(0));
assertEquals("second", mockedList.get(1));
assertEquals(10, mockedList.size());
// 4. 使用 thenAnswer 动态生成返回值
when(mockedList.get(anyInt())).thenAnswer(invocation -> {
int index = invocation.getArgument(0);
return "element_" + index;
});
assertEquals("element_5", mockedList.get(5));
// 5. 使用 spy 包装真实对象
List<String> realList = new ArrayList<>();
List<String> spiedList = spy(realList);
spiedList.add("real");
assertEquals(1, spiedList.size());
assertEquals("real", spiedList.get(0));
// 6. stub spy 的部分方法
when(spiedList.size()).thenReturn(100);
assertEquals(100, spiedList.size());
// 7. 验证交互
verify(mockedList).get(0);
verify(mockedList, times(2)).get(anyInt());
verify(spiedList).add("real");
// 8. 使用参数匹配器
when(mockedList.contains(anyString())).thenReturn(true);
assertTrue(mockedList.contains("anything"));
// 9. 验证调用顺序
mockedList.add("first");
mockedList.add("second");
InOrder inOrder = inOrder(mockedList);
inOrder.verify(mockedList).add("first");
inOrder.verify(mockedList).add("second");
}
}
8. 总结
Mockito 是一个功能强大且灵活的 mocking 框架,本教程涵盖了它的核心功能:
mock()
- 创建 mock 对象spy()
- 创建 spy 对象(部分 mock)when().thenReturn()
- 配置 mock 行为thenAnswer()
- 动态生成返回值- 参数匹配器 (
any()
,eq()
,argThat()
等) - 灵活匹配参数 verify()
- 验证交互行为
Mockito有效协助我们编写单元测试用例,提高代码健壮性,让测试变得简单、专注而高效。Mockito另外一大优点是可以跟Spring 测试框架无缝集成,有专门的注解支持spring注入。