单元测试中@BeforeStep先于@Before执行,如何提前初始化Mock?
解决Spring Batch Reader测试中@BeforeStep早于测试@Before执行的问题
这个问题我之前也碰到过,核心原因就是Spring Batch的@BeforeStep生命周期回调时机,比测试类里@Before方法的执行时机更早——如果你的测试上下文启动时自动触发了Job/Step的执行,那@BeforeStep会在测试的mock设置完成前就跑了,自然拿不到预期的mock返回值。下面给你几个可行的解决方案:
方案一:用Spring Batch测试框架控制Step执行时机
Spring Batch专门提供了@SpringBatchTest注解来简化测试,配合JobLauncherTestUtils可以手动控制Step的启动时机,确保mock设置完成后再触发@BeforeStep。
修改测试类代码:
@RunWith(SpringRunner.class) @SpringBatchTest // 新增这个注解,自动配置测试工具类 @SpringBootTest(classes = NedBatchApplication.class) public class CustomReaderTest { @Autowired private JobLauncherTestUtils jobLauncherTestUtils; // 用于手动启动Step @Autowired private CustomReader customReader; @Mock private RestApiService restApiService; @Before public void setup() { MockitoAnnotations.initMocks(this); // 把mock注入到Reader实例 ReflectionTestUtils.setField(customReader, "restApiService", restApiService); // 设置mock的返回值 Mockito.when(restApiService.getData()).thenReturn(expectedData); } @Test public void testReaderBehavior() throws Exception { // 手动启动目标Step,此时@BeforeStep会在mock设置好之后执行 JobExecution jobExecution = jobLauncherTestUtils.launchStep("你的Step名称"); // 验证Step执行状态 Assert.assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); // 验证mock被正确调用 Mockito.verify(restApiService, Mockito.times(1)).getData(); } }
方案二:将Reader改为StepScoped,用@MockBean替换@Mock
如果你的Reader不需要在上下文启动时就初始化,可以把它标记为@StepScope,这样Reader实例会在Step启动时才创建。再配合@MockBean让Spring自动替换上下文里的RestApiService为mock,就能保证@BeforeStep执行时mock已经准备就绪。
第一步:修改CustomReader
@Component @StepScope // 标记为Step作用域 public class CustomReader implements ItemReader<SomeDTO> { private final RestApiService restApiService; private SomeDTO someDTO; // 改用构造注入,比字段注入更安全,也方便测试 public CustomReader(RestApiService restApiService) { this.restApiService = restApiService; } @BeforeStep public void initialize() { // 建议改成public,Spring反射调用更可靠 someDTO = restApiService.getData(); } @Override public SomeDTO read() { // ... 你的读取逻辑 return someDTO; } }
第二步:修改测试类
@RunWith(SpringRunner.class) @SpringBatchTest @SpringBootTest(classes = NedBatchApplication.class) public class CustomReaderTest { @Autowired private JobLauncherTestUtils jobLauncherTestUtils; @MockBean // 用@MockBean代替@Mock,Spring会自动替换上下文里的实例 private RestApiService restApiService; @Before public void setup() { // 直接设置mock返回值即可,不需要手动注入 Mockito.when(restApiService.getData()).thenReturn(expectedData); } @Test public void testReader() throws Exception { JobExecution jobExecution = jobLauncherTestUtils.launchStep("你的Step名称"); Assert.assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); Mockito.verify(restApiService).getData(); } }
方案三:手动触发@BeforeStep方法(适合单独测试Reader逻辑)
如果只是想测试Reader的read()方法,不想启动整个Step,可以手动调用@BeforeStep标注的方法(注意原方法是private的,需要用反射调用,或者改成public/protected)。
测试类代码:
@RunWith(SpringRunner.class) @SpringBootTest(classes = NedBatchApplication.class) public class CustomReaderTest { @Autowired private CustomReader customReader; @Mock private RestApiService restApiService; @Before public void setup() { MockitoAnnotations.initMocks(this); ReflectionTestUtils.setField(customReader, "restApiService", restApiService); Mockito.when(restApiService.getData()).thenReturn(expectedData); // 手动调用@BeforeStep的initialize方法 try { Method initializeMethod = CustomReader.class.getDeclaredMethod("initialize"); initializeMethod.setAccessible(true); // 突破private限制 initializeMethod.invoke(customReader); } catch (Exception e) { throw new RuntimeException("调用initialize方法失败", e); } } @Test public void testReadMethod() { SomeDTO result = customReader.read(); Assert.assertEquals(expectedData, result); // 验证mock被调用 Mockito.verify(restApiService).getData(); } }
另外提个小建议:原代码里@BeforeStep标注的是private方法,虽然Spring Batch可能通过反射能调用,但改成public或者protected会更规范,也避免潜在的反射问题。
内容的提问来源于stack exchange,提问作者Groot
相关产品推荐
相关产品推荐

