java单元测试中不应mock集合类本身,而应使用真实集合触发fail-fast机制(如concurrentmodificationexception),mock外部数据源(如dao),用spy验证集合操作调用,手写iterator实现控制状态,对stream终端结果断言。

在 Java 单元测试中 Mock 复杂集合与迭代器行为,关键不是去 mock ArrayList、HashMap 或 Iterator 本身,而是控制被测代码的输入与交互路径,让真实集合自然触发预期行为(如 ConcurrentModificationException),或让外部依赖返回可控数据,从而间接验证集合处理逻辑。直接 mock 集合类容易导致行为失真、断言失败,属于常见误区。
别 mock 集合本身,用真实集合构造场景
ArrayList、LinkedList、HashMap 等自带 fail-fast 迭代器机制。要测试迭代中修改结构引发的 ConcurrentModificationException,必须使用真实实例:
- 创建
new ArrayList()并填充初始元素 - 在 for-each 或
iterator().next()循环内调用list.add()或map.remove() - 用
assertThrows<concurrentmodificationexception>(() -> { ... })</concurrentmodificationexception>断言异常抛出 - 避免
mock(ArrayList.class)—— 它不会执行真实 add/remove,迭代器也不检测 modCount,根本不会抛异常
Mock 外部数据源,而非集合操作
当被测方法内部构建集合(如从数据库查出 List、解析 JSON 成 List),应 mock 数据获取环节,保留集合的真实行为:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- Mock DAO 或 FeignClient,让
repository.findAll()返回预设的Arrays.asList(a, b, c) - 若方法中对 List 做 filter/map/sort/collect,这些 Stream 操作无需 mock —— JDK 行为稳定可信
- 需要验证是否调用了
list.add()?用doAnswer或spy记录调用,而不是 stub 它的返回值 - 例如:
List<string> spyList = spy(new ArrayList()); doNothing().when(spyList).add(any());</string>—— 仅用于验证调用,不改变状态
迭代器行为需靠真实对象 + 显式 next() 控制
Iterator 的 hasNext()/next() 是典型“状态机”,mock 它容易断裂。更可靠的做法是:
- 用
Collections.unmodifiableList(...)或Arrays.asList()提供只读视图,测试遍历时不可修改的边界 - 若被测逻辑接收
Iterator<t></t>参数,可手写简易实现类,控制next()返回顺序或在第 N 次抛异常 - 避免
mock(Iterator.class)后反复when(it.next()).thenReturn(...)—— 无法模拟多轮 hasNext/next 协作,易漏状态 - 示例:构造一个循环三次后抛
NoSuchElementException的自定义 Iterator,比 mock 更清晰、可复现
Stream 流式处理,专注断言终端结果
Stream 是声明式、惰性求值的管道,mock 中间操作(如 filter、map)既没必要也不可行。重点在于:
- 确保输入流源头(如 repository 方法)返回确定数据
- 对最终
collect(toList())结果做内容断言,用 AssertJ 的containsExactly(...)或extracting("field") - 若 map 中调用外部服务(如
userClient.findById(id)),mock 那个 client,而不是 map 本身 - 复杂转换逻辑提取为独立方法(如
toOrderDto()),单独测试或注入 mock 依赖
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










