
在 spring batch 中,步骤内抛出的异常不会直接传播到测试调用栈,而是被封装进 `jobexecution.failureexceptions` 集合;因此需通过检查该集合而非 `assertthrows` 来验证异常是否发生。
Spring Batch 的执行模型具有强事务与容错语义:当 Tasklet.execute() 或其他步骤组件(如 ItemProcessor)抛出未捕获异常时,框架会主动捕获该异常,完成当前事务回滚,并将异常记录在 JobExecution 实例的 failureExceptions 属性中,而非向调用方重新抛出。这正是你使用 assertThrows(...) 失败的根本原因——jobLauncherTestUtils.launchJob(...) 方法返回的是一个已完成的 JobExecution 对象(即使失败),而不会让底层异常“穿透”出来。
要正确验证异常是否按预期发生,应改为:
- 调用 launchJob(...) 获取 JobExecution;
- 检查其 getStatus() 是否为 FAILED(可选但推荐);
- 断言 failureExceptions 非空且包含目标异常类型(及可选消息)。
以下是推荐的测试写法(使用 JUnit 5 + AssertJ,更清晰、健壮):
@Test
void testJobThrowsMyException() {
// given
JobParameters params = new JobParameters();
// when
JobExecution jobExecution = jobLauncherTestUtils.launchJob(params);
// then
assertThat(jobExecution.getStatus()).isEqualTo(BatchStatus.FAILED);
assertThat(jobExecution.getFailureExceptions())
.hasSize(1)
.first()
.isInstanceOf(IllegalStateException.class)
.hasMessage("Thats the exception i want to test");
}⚠️ 注意事项:
- jobLauncherTestUtils.launchJob(...) 是同步阻塞调用,会等待作业完全结束(包括所有错误处理逻辑),因此 JobExecution 状态和 failureExceptions 已就绪,可安全断言;
- 不要尝试在 launchJob 外层加 try-catch 或 assertThrows——这违背了 Spring Batch 的异常处理契约;
- 若作业配置了 SkipPolicy 或 RetryTemplate,异常可能被吞没或重试,此时 failureExceptions 可能为空或包含多个嵌套异常,需结合实际配置分析;
- 在集成测试中,确保 JobLauncherTestUtils 已正确注入(通常通过 @SpringBootTest 和 @Import(JobConfiguration.class) 加载完整上下文)。
总结:Spring Batch 的异常是“作业级”的可观测事件,而非“方法级”的控制流信号。掌握 JobExecution.failureExceptions 这一核心机制,是编写可靠批处理测试的关键前提。










