0

I am trying to unit test and individual step in Spring Batch. This step has a reader, a processor and a writer. The reader has a method annotated with @BeforeStep in order to set StepExecution.

I want to add a key to StepExecution in order to promote it to future steps using a listener. Here is my configuration:

@Configuration
@EnableBatchProcessing
public class MyJob {

    @Bean
    public Job myJob() {
        return jobBuilderFactory.get("myJob")
                .incrementer(new RunIdIncrementer())
                .listener(jobIdToContextExecutionListener())
                .flow(myStep())
                .end()
                .build();
    }

    @Bean
    public Step myStep() {
        return stepBuilderFactory.get("myStep").<MyDto, OtherDto>chunk(chunk)
                .reader(myReader())
                .processor(myProcessor())
                .writer(myWriter(JOB_ID))
                .listener(myPromotionListener())
                .build();
    }

    @Bean
    public ExecutionContextPromotionListener myPromotionListener() {
        ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener();
        listener.setKeys(new String[] {"myKey"});
        return listener;
    }

    @Bean
    @StepScope
    ItemReader<DTO> myReader() {
        return new MyReader();
    }

    @Bean
    @StepScope
    ItemProcessor<MyDto> myProcessor() {
        return new MyProcessor();
    }
}

When I want to unit test this step, I get NullPointerException for stepExecution in sendForNextSteps method. Here is the code for my ItemReader:

public class MyReader implements ItemReader<MyDto> {

    private StepExecution stepExecution;

    @Override
    public MyDto read() {
        // some logic
                
        sendForNextSteps(someDto);
        return someDto;
    }

   private void sendForNextSteps(MyDto myDto){
            ExecutionContext stepContext = this.stepExecution.getExecutionContext();
            stepContext.put("mykey", myDto);
    }

    @BeforeStep
    public void saveStepExecution(StepExecution stepExecution) {
        this.stepExecution = stepExecution;
    }
}

Here is my code for the unit test:

@SpringBootTest
@ContextConfiguration(classes = {MyJob.class, JobLauncherTestUtilsWithoutDatasourceConfig.class, TransferItemService.class, DataUtils.class})
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class})
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class JobStepUnitTest {

    @Test
    public void myTest() {

        /**
         * some mocking and arrangement irrelevant of spring batch
         */


        /**
         * when launching myStep
         */
        JobExecution jobExecution = jobLauncherTestUtils.launchStep("myStep");



        /**
         * some assertions on the number of calls to methods. also irrelevant to spring batch
         */
        
    }
}

Is there any way to unit test an individual step that manipulates StepContext (for example in my code, ItemReader adds a key to the context)? I have seen examples that unit test a StepScope component like ItemReader but could not find a way to unit test the whole step. Thanks in advance.

I have also checked that answer in the following link:

link

but the difference is that in this link, we are testing a StepScope component. In my case, we are testing the whole step using jobLauncherTestUtils.launchStep(). I also tried adding getStepExecution method in my test class together with StepScopeTestExecutionListener.class for the @TestExecutionListeners annotation but no success

  • Please share the code of your test to see why the step execution is not initialized. – Mahmoud Ben Hassine Aug 24 '22 at 18:16
  • Thanks Mahmoud. I added unit test code and more explanation. Thanks a lot. – Hesam Ghiasi Aug 25 '22 at 06:29
  • Thank you for the update. This is the same question as in https://stackoverflow.com/questions/52190553/beforestep-method-is-not-called-during-unit-test-execution (the only difference is that the other question is about the reader, not the processor, but the idea is the same). – Mahmoud Ben Hassine Aug 25 '22 at 07:19
  • Thanks @MahmoudBenHassine for mentioning that link. I had seen that before posting this question but I think my case is different. I updated the question and explained the difference. – Hesam Ghiasi Aug 26 '22 at 09:22
  • I tried something similar to your setup and the test passes without any issue, see example here: https://github.com/fmbenhassine/spring-batch-lab/tree/main/issues/so73476578. Hope this helps. – Mahmoud Ben Hassine Aug 26 '22 at 16:18
  • The problem was the bean definition. Returning bean of type ItemReader prevents Spring from accessing the method annotated with @BeforeStep so I have to return a bean of type MyItemReader. – Hesam Ghiasi Aug 30 '22 at 07:50
  • Not sure this is the reason, in the example I shared the return type of the bean definition method is [`ItemReader`](https://github.com/fmbenhassine/spring-batch-lab/blob/main/issues/so73476578/src/main/java/com/example/demo/SO73476578.java#L64) and yet the method annotated with `@BeforeStep` is correctly introspected. – Mahmoud Ben Hassine Aug 30 '22 at 08:00
  • I guess the only difference is annotating myReader() with Bean on instead of StepScope – Hesam Ghiasi Oct 03 '22 at 12:05

0 Answers0