Spring Batch测试添加自定义Extension后@MockBean注入异常问题
问题:Spring Batch测试添加自定义SpringExtension后@MockBean注入失败
原本正常运行的Spring Batch Job测试类,在添加继承SpringExtension的自定义扩展类MyExtension后,出现如下错误:
nested exception is java.lang.IllegalStateException: The field de.orgen.batchprocessing2.DownloadService de.orgen.batchprocessing2.chunkJobsTest.downloadService cannot have an existing value
完整栈追踪:
org.springframework.beans.factory.BeanCreationException: Could not inject field: de.orgen.batchprocessing2.DownloadService de.orgen.batchprocessing2.chunkJobsTest.downloadService; nested exception is java.lang.IllegalStateException: The field de.orgen.batchprocessing2.DownloadService de.orgen.batchprocessing2.chunkJobsTest.downloadService cannot have an existing value at org.springframework.boot.test.mock.mockito.MockitoPostProcessor.inject(MockitoPostProcessor.java:392) ...(省略部分栈追踪) Caused by: java.lang.IllegalStateException: The field de.orgen.batchprocessing2.DownloadService de.orgen.batchprocessing2.chunkJobsTest.downloadService cannot have an existing value at org.springframework.util.Assert.state(Assert.java:94) at org.springframework.boot.test.mock.mockito.MockitoPostProcessor.inject(MockitoPostProcessor.java:386) ... 71 more
使用环境:Spring Batch 4.1.2、Spring Boot 2.1.4.RELEASE,版本无法变更。
相关代码
测试类代码
@SpringBootTest @SpringJUnitConfig @ExtendWith(MyExtension.class) public class chunkJobsTest { @Autowired private Job secondJob; @Autowired private JobLauncher jobLauncher; @MockBean DownloadService downloadService; @Autowired private JobRepository jobRepository; private JobLauncherTestUtils jobLauncherTestUtils; @BeforeEach public void initializeJobLauncherTestUtils() { this.jobLauncherTestUtils = new JobLauncherTestUtils(); this.jobLauncherTestUtils.setJobLauncher(jobLauncher); this.jobLauncherTestUtils.setJobRepository(jobRepository); this.jobLauncherTestUtils.setJob(secondJob); } @Test void testing(){ Map<String, JobParameter> param = new HashMap<>(); param.put("test", new JobParameter("test1")); when(this.downloadService.downloadFile()).thenReturn("mocked file"); JobExecution jobExecution = jobLauncherTestUtils.launchStep("step1Chunk", new JobParameters(param)); } }
配置类代码
@Configuration @EnableBatchProcessing public class ChunkJobs { @Autowired JobRepository jobRepository; @Autowired private FirstItemReader reader; @Autowired private FirstItemProcessor processor; @Autowired private FIrstItemWriter writer; @Autowired private JobBuilderFactory jobBuilderFactory; @Autowired private StepBuilderFactory stepBuilderFactory; @Bean public Job secondJob(Step step1Chunk){ return jobBuilderFactory.get("myJob2") .incrementer(new RunIdIncrementer()) .start(step1Chunk) .build(); } @Bean("step1Chunk") public Step step1Chunk(){ return stepBuilderFactory.get("step1Chunk") .<Integer, Long>chunk(3) .reader(reader) .processor(processor) .writer(writer) .build(); } }
Reader代码
@Component public class FirstItemReader implements ItemReader<Integer> { @Autowired DownloadService downloadService; List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); int index =0; @Override public Integer read() throws Exception { System.out.println("in itemReader"); System.out.println(this.downloadService.downloadFile()); Integer item ; if(index<list.size()){ item = list.get(index); index++; return item; } return null; } }
自定义扩展类代码
public class MyExtension extends SpringExtension { }
解决方案
该问题源于Spring Boot 2.1.x版本中,SpringExtension与@MockBean的处理时机冲突,导致Mock字段被提前初始化,触发注入断言失败。以下是可行的解决方式:
方式一:替换@MockBean为手动注册Mock
移除测试类上的@MockBean注解,手动创建Mock并注册为Spring Bean:
@SpringBootTest @SpringJUnitConfig @ExtendWith(MyExtension.class) public class chunkJobsTest { @Autowired private Job secondJob; @Autowired private JobLauncher jobLauncher; @Autowired private DownloadService downloadService; // 注入手动注册的Mock @Autowired private JobRepository jobRepository; private JobLauncherTestUtils jobLauncherTestUtils; // 静态配置类注册Mock @Configuration static class TestMockConfig { @Bean public DownloadService downloadService() { return Mockito.mock(DownloadService.class); } } @BeforeEach public void initializeJobLauncherTestUtils() { this.jobLauncherTestUtils = new JobLauncherTestUtils(); this.jobLauncherTestUtils.setJobLauncher(jobLauncher); this.jobLauncherTestUtils.setJobRepository(jobRepository); this.jobLauncherTestUtils.setJob(secondJob); } @Test void testing(){ Map<String, JobParameter> param = new HashMap<>(); param.put("test", new JobParameter("test1")); when(this.downloadService.downloadFile()).thenReturn("mocked file"); JobExecution jobExecution = jobLauncherTestUtils.launchStep("step1Chunk", new JobParameters(param)); } }
方式二:调整扩展类的生效方式
如果不需要直接通过@ExtendWith声明扩展,可以将MyExtension改为通过Spring上下文初始化器生效:
- 让
MyExtension实现ApplicationContextInitializer接口 - 在测试类上添加
@ContextConfiguration(initializers = MyExtension.class),移除@ExtendWith(MyExtension.class)
方式三:重写扩展类的生命周期方法
在MyExtension中重写postProcessTestInstance方法,确保字段在Mockito处理前未被赋值:
public class MyExtension extends SpringExtension { @Override public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception { // 先调用父类方法,确保Spring上下文初始化完成 super.postProcessTestInstance(testInstance, context); // 自定义逻辑放在这里 } }
内容的提问来源于stack exchange,提问作者Gerard
相关产品推荐
相关产品推荐

