
本文详解如何在 Mockito 单元测试中正确模拟 List.add() 行为(尤其在 Iterator 遍历循环内),避免因错误地 mock ArrayList 实例导致断言失败,并指出关键误区:不应 mock 列表本身,而应确保真实列表被填充,或精准 stub add() 方法匹配任意目标类型。
本文详解如何在 mockito 单元测试中正确模拟 `list.add()` 行为(尤其在 `iterator` 遍历循环内),避免因错误地 mock 列表本身导致断言失败,并指出关键误区:不应 mock 列表本身,而应确保真实列表被填充,或精准 stub `add()` 方法匹配任意目标类型。
在测试类似 getUsers() 这类依赖外部服务(如 DynamoDB)并内部构建 List
✅ 正确做法是:使用真实列表(new ArrayList())作为被测方法内部容器,仅 mock 外部依赖(DynamoDB 相关组件),并确保 Util.jsonToObject(...) 在测试中能返回可控对象(可通过 @MockitoSettings(strictness = Strictness.LENIENT) 或直接 spy/替换工具类实现)。若必须 stub add()(例如验证调用行为),应使用 doReturn(true).when(realList).add(any(Users.class)) —— 但注意:这仅验证调用,不改变列表状态;真正需要断言内容时,必须让 jsonToObject 返回有效实例。
以下是推荐的、可运行的测试结构(基于 JUnit 5 + Mockito 4+):
@ExtendWith(MockitoExtension.class)
class DynamoDBServiceTest {
@Mock
private DynamoDB dynamoDB;
@Mock
private Table table;
@Mock
private Index index;
@Mock
private ItemCollection<queryoutcome> itemCollection;
@Mock
private Iterator<item> iterator; // 使用标准 Iterator,非 IteratorSupport
@Mock
private Item item;
private DynamoDBService dynamoDBService;
@BeforeEach
void setUp() {
// 真实初始化被测服务(注入 mock 的 dynamoDB)
dynamoDBService = new DynamoDBService(dynamoDB);
// 构建 mock 链
when(dynamoDB.getTable("users")).thenReturn(table);
when(table.getIndex("userIndex")).thenReturn(index);
when(index.query(any(QuerySpec.class))).thenReturn(itemCollection);
when(itemCollection.iterator()).thenReturn(iterator);
// 模拟迭代器行为:一次 hasNext → true,第二次 → false
when(iterator.hasNext()).thenReturn(true, false);
when(iterator.next()).thenReturn(item);
when(item.toJSONPretty()).thenReturn("{\"id\":\"1\",\"name\":\"Alice\"}");
}
@Test
void getUsers_returnsPopulatedList() {
// Arrange: 准备期望的 Users 对象(需确保 Util.jsonToObject 能解析)
Users expectedUser = new Users("1", "Alice", null, "");
// ⚠️ 关键:不要 mock ArrayList!让方法内部创建真实列表
// 我们只需确保 jsonToObject 返回预期对象(此处假设 Util 是静态工具类)
// 若 Util 不可测,建议将其抽象为可注入的依赖(如 JsonConverter 接口)
// Act
List<users> result = dynamoDBService.getUsers("test-id");
// Assert
assertThat(result).hasSize(1);
assertThat(result.get(0)).usingRecursiveComparison()
.isEqualTo(expectedUser);
}
}</users></item></queryoutcome>
? 重要注意事项:
- 永远避免 mock(ArrayList.class):它破坏了 List 的契约,使 get()、size() 等方法不可用,违背测试真实性。
- doReturn().when(list).add(...) 仅用于验证调用,不替代数据构造:若想验证“是否调用了 add”,可用 verify(list).add(any(Users.class));但若要断言返回列表内容,请确保 jsonToObject 返回真实对象。
-
Util.jsonToObject 是测试盲点:当前代码中它未被 mock 或控制,会导致测试不稳定。最佳实践是将其封装进接口(如 JsonConverter
),并在测试中注入 mock(JsonConverter.class) 并 stub convert(..., Users.class)。 - 优先使用 @Mock 和 @InjectMocks 注解:提升可读性与维护性,减少手动 mock() 和 when() 嵌套。
- doReturn(...).when(...) vs when(...).thenReturn(...):当 mock 对象方法有副作用(如 void 方法或 final 方法)时,doReturn 是唯一选择;对普通方法二者等效,但 doReturn 更安全(避免提前触发 real method)。
总结:模拟的核心是「隔离外部依赖」,而非「模拟内部数据结构」。让业务逻辑在真实集合上运行,只对 I/O、网络、第三方工具类等不可控部分进行 mock —— 这才是可靠、可维护的单元测试之道。










