spring-projects / spring-batch

Spring Batch is a framework for writing batch applications using Java and Spring
http://projects.spring.io/spring-batch/
Apache License 2.0
2.7k stars 2.33k forks source link

JobScopeTestExecutionListener invokes methods with JobExecution return type #3976

Open marschall opened 3 years ago

marschall commented 3 years ago

Bug description JobScopeTestExecutionListener invokes any method with the JobExecution return type. Consider the following test with uses a utility method to launch a job. The method will be invoked by JobScopeTestExecutionListener before the @BeforeEach and @Test method. In this case causing the test to fail because the jobParameters is null.

class MyBatchTests {

  private JobParameters jobParameters;

  @BeforeEach
  void setUp() {
    this.jobParameters = ...;
  }

  private JobExecution launchJobUtilityMethod() throws Exception {
    return this.jobLauncherTestUtils.launchJob(this.jobParameters);
  }

  @Test
  void batchTest() throws Exception {
    JobExecution jobExecution = this.launchJobUtilityMethod();
    assert...;
  }

}

Environment Spring Batch version: 4.3.3 Java version: 11.0.12 Database: H2 1.4.200

Steps to reproduce

Expected behavior org.springframework.batch.test.JobScopeTestExecutionListener#getJobExecution(TestContext) looks only at fields as the comment says.

Minimal Complete Reproducible example

@SpringBatchTest
class JobScopeTestExecutionListenerTests {

  private JobExecution shouldNeverBeInvoked(String s, int i) {
    throw new IllegalStateException("should never be invoked");
  }

  @Test
  void testMethod() throws Exception {
  }

  @Configuration
  @EnableBatchProcessing
  static class ContextConfiguration {

    private static final Log LOGGER = LogFactory.getLog(MethodHandles.lookup().lookupClass());

    @Autowired
    public JobBuilderFactory jobBuilderFactory;

    @Autowired
    public StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job loggingJob() {
      return this.jobBuilderFactory.get("loggingJob")
        .incrementer(new RunIdIncrementer())
        .start(this.step1())
        .build();
    }

    @Bean
    public Step step1() {
      return this.stepBuilderFactory.get("step1")
        .tasklet(this.loggingTasklet("step1"))
        .build();
    }

    @Bean
    public DataSource dataSource() {
      return new EmbeddedDatabaseBuilder()
              .generateUniqueName(true)
              .setType(H2)
              .build();
    }

    private Tasklet loggingTasklet(String stepName) {
      CallableTaskletAdapter taskletAdapter = new CallableTaskletAdapter();
      taskletAdapter.setCallable(() -> {
        LOGGER.info("executing step: " + stepName);
        return RepeatStatus.FINISHED;
      });
      return taskletAdapter;
    }

  }

}
hpoettker commented 3 years ago

I'm afraid, it's not a bug but a feature. 😄 The javadoc on JobScopeTestExecutionListener#getJobExecution is indeed misleading but the comment on class level is correct. And it's also described in the reference documentation: https://docs.spring.io/spring-batch/docs/4.3.x/reference/html/testing.html#testing-step-scoped-components

I was also quite surprised when I stumbled on this in a similar manner, and I'm not sure whether it's a good idea that the listeners are added with @SpringBatchTest automatically. In particular, since there is no straight-forward way to disable them, if I still want the beans from BatchTestContextCustomizer.

Maybe an annotation like e.g. @TestJobExecution on the method would be a sensible requirement for users to enable this feature.