应通过真实集合在迭代中修改结构来触发ConcurrentModificationException,而非Mockito模拟;JUnit 5中用assertThrows断言异常类型与消息,确保单线程fail-fast场景可复现。

在 Mockito 单元测试中模拟 ConcurrentModificationException,核心在于:**不能靠 Mockito 直接“mock 异常”,而是要让被测代码实际触发该异常**——因为 ConcurrentModificationException 是 JVM 在迭代过程中检测到集合结构被并发修改时自动抛出的运行时异常,它不是由业务逻辑主动 throw 的,也无法被 Mockito 的 when().thenThrow() 精准拦截(除非你 mock 的是某个会显式 throw 它的方法)。
明确触发场景:用真实集合 + 迭代中修改
最可靠、最贴近真实行为的方式,是构造一个会自然抛出 ConcurrentModificationException 的场景:
- 使用
ArrayList或HashMap等非线程安全集合(它们的迭代器有 fail-fast 机制) - 在 for-each 循环或
Iterator.next()过程中,调用集合自身的add()、remove()、clear()等结构性修改方法 - 此时 JVM 迭代器会立即检测并抛出
ConcurrentModificationException
✅ 示例(JUnit 5):
@Test
void shouldThrowConcurrentModificationExceptionWhenModifyingDuringIteration() {
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
<pre class="brush:php;toolbar:false;">// 使用普通 for-each(底层用 Iterator)
Exception exception = assertThrows(ConcurrentModificationException.class, () -> {
for (String s : list) {
if ("b".equals(s)) {
list.add("d"); // ⚠️ 迭代中修改结构 → 触发 CME
}
}
});
assertTrue(exception.getMessage().contains("Concurrent modification"));}
立即学习“Java免费学习笔记(深入)”;
不推荐:Mockito 模拟 throw CME(仅适用于特定接口方法)
如果你的被测类依赖某个接口(比如 DataService),而该接口的某个方法 明确声明或实际会 throw ConcurrentModificationException(例如封装了上述逻辑并向上抛出),那么可以用 Mockito 模拟:
- 确保该方法签名允许抛出该异常(即声明了
throws ConcurrentModificationException,或它是 RuntimeException) - 用
when(mock.method()).thenThrow(new ConcurrentModificationException())
⚠️ 注意:这不是模拟“集合迭代异常”,而是模拟“某方法抛出了这个异常”。它绕过了真实触发机制,测试的是异常处理逻辑,而非并发修改行为本身。
示例:
DataService mockService = mock(DataService.class);
when(mockService.fetchItems()).thenThrow(new ConcurrentModificationException("forced"));
<p>// 被测方法中 catch 并处理这个异常
assertThatThrownBy(() -> target.processItems()).isInstanceOf(RuntimeException.class);验证异常信息与类型(JUnit 4/5 都适用)
无论哪种方式,断言都应聚焦两点:
- 异常类型必须是
ConcurrentModificationException(或其子类,但通常就是它) - 异常消息可选校验(如含 "concurrent"、"modification" 等关键词),增强可读性
JUnit 5 推荐写法:
ConcurrentModificationException thrown = assertThrows(
ConcurrentModificationException.class,
() -> { /* 触发代码 */ }
);
assertThat(thrown).hasMessageContaining("concurrent");避免常见误区
❌ 不要用 @Test(expected = ConcurrentModificationException.class)(JUnit 4 旧语法,不推荐)
❌ 不要试图 mock ArrayList 或 Iterator 来伪造 CME —— 它们是 JDK 类,mock 后行为不可控且失去测试意义
❌ 不要在多线程环境下用简单 sleep + 多线程修改来“模拟并发”做单元测试 —— 非确定、难调试、不属于单元测试范畴(应交由集成测试或 JMH)
真实、可控、可复现的单线程 fail-fast 场景,才是单元测试中验证 ConcurrentModificationException 的正确姿势。


















