
在 spring batch 中,步骤内抛出的异常不会直接向测试方法传播,而是被封装进 `jobexecution.failureexceptions` 集合中;因此直接用 `assertthrows` 会失败,需通过检查 `jobexecution` 对象来验证异常是否发生。
Spring Batch 的执行模型具有强事务与容错语义:当 Tasklet.execute() 或 ItemProcessor 等组件抛出未捕获异常时,框架会拦截该异常,完成当前 step 的回滚,并将其记录在 JobExecution 实例的 failureExceptions 属性中,而非向上层(如 jobLauncherTestUtils.launchJob())重新抛出。这正是 assertThrows(IllegalStateException.class, ...) 失败的根本原因——方法调用本身“成功返回”了一个 JobExecution 对象,异常已被静默捕获并归档。
要正确验证异常行为,应改用以下模式:
@Test
void testJobThrowsMyException() {
// Given
JobParameters params = new JobParameters();
// When
JobExecution jobExecution = jobLauncherTestUtils.launchJob(params);
// Then
assertThat(jobExecution.getFailureExceptions())
.isNotEmpty()
.anySatisfy(throwable ->
assertThat(throwable).isInstanceOf(IllegalStateException.class)
.hasMessage("Thats the exception i want to test")
);
}✅ 关键点说明:jobLauncherTestUtils.launchJob(...) 总是返回 JobExecution(即使执行失败),绝不会抛出原始业务异常;jobExecution.getFailureExceptions() 返回 List,包含该 job 执行过程中所有 step 级别的致命异常(非跳过/重试类异常);若需校验多个异常或嵌套原因,可结合 getCause() 或 getSuppressed() 进行深度断言;使用 AssertJ(如示例)能显著提升断言可读性与健壮性;若使用 JUnit 5 原生断言,可改用:assertTrue(jobExecution.getFailureExceptions().stream() .anyMatch(t -> t instanceof IllegalStateException && "Thats the exception i want to test".equals(t.getMessage())));
此外,请确保测试环境已正确配置 JobLauncherTestUtils(通常通过 @ContextConfiguration 或 @ImportAutoConfiguration 加载 Batch 测试支持),且待测 job 的 step 确实处于 非跳过、非重试 的失败路径上(例如:FaultTolerantStepBuilder 中未配置 skip() 或 retry() 规则)。否则异常可能被框架吞没或转化为其他状态,导致 failureExceptions 为空。
总结:Spring Batch 的异常传播是面向作业生命周期设计的,测试时必须“顺流而下”,从 JobExecution 出发进行断言,而非期望顶层 API 抛出底层异常——这是理解其测试范式的核心前提。










