You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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上下文初始化器生效:

  1. 让MyExtension实现ApplicationContextInitializer接口
  2. 在测试类上添加@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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 06:34:53