
本文解释为何直接模拟 list 会导致单元测试行为异常,并指出应优先使用真实列表实例而非 mockito 模拟——因为 list 是值对象,其行为不可靠地被模拟,且模拟会掩盖逻辑缺陷。
本文解释为何直接模拟 list 会导致单元测试行为异常,并指出应优先使用真实列表实例而非 mockito 模拟——因为 list 是值对象,其行为不可靠地被模拟,且模拟会掩盖逻辑缺陷。
在编写单元测试时,一个常见误区是“为 mock 而 mock”——尤其当团队规范强调“只测试一个类、其余全 mock”时,开发者容易机械套用规则,忽视被模拟对象的本质。上述 Sum.sum(List<integer>)</integer> 的测试失败正是典型例证:
final List<Integer> listMock = Mockito.mock(List.class); Mockito.when(listMock.get(0)).thenReturn(42); assertFalse(listMock.isEmpty()); // ✅ 通过(但纯属巧合!) assertEquals(42, listMock.get(0)); // ✅ 通过(仅因显式 stub) assertEquals(42, Sum.sum(listMock)); // ❌ 失败:返回 0
问题根源在于:Mockito.mock(List.class) 创建的是一个“空壳对象”,它不具有 List 的内在一致性语义。
-
isEmpty()默认返回false(boolean 类型的默认值); -
size()默认返回0(int 类型的默认值); -
get(0)虽被 stub 为42,但size() == 0导致循环for (int i = 0; i 根本不执行——因此求和结果为 <code>0。
这造成逻辑矛盾:列表“非空”却“大小为 0”,违反 List 接口契约。这种“僵尸对象”看似工作,实则脆弱——一旦生产代码将 isEmpty() 替换为 size() == 0,或反之,测试即失效,却无法暴露真实缺陷。
✅ 正确做法:使用真实、不可变的列表实例。List 是典型的 value object(值对象):仅承载数据,无副作用、无外部依赖、构造廉价。Mockito 官方明确建议:不要模拟你未定义的类型,更不要模拟值对象。
@Test
void sum_single() {
List<Integer> input = List.of(42); // 真实、轻量、语义完整
assertEquals(42L, Sum.sum(input));
}
@Test
void sum_multiple() {
List<Integer> input = List.of(1, 2, 3, 4);
assertEquals(10L, Sum.sum(input));
}
@Test
void sum_empty() {
List<Integer> input = List.of();
assertEquals(-1L, Sum.sum(input));
}? 关键原则重申:
- Unit ≠ Single Class:一个测试单元可以是多个紧密协作的类(如
Sum+List),只要它们共同构成一个可验证的行为边界;- Mock 仅用于解耦复杂依赖:如数据库、HTTP 客户端、时间服务等有 I/O、状态或难以控制的组件;
- 真实对象优先(Real over Mock):对
String、Integer、LocalDateTime、List、Map等 JDK 值类型,直接实例化更安全、更清晰、更高效。
最终,删掉 Mockito.mock,拥抱 List.of() —— 你的测试将更简洁、更可靠、更贴近真实运行场景。

















