spring-batch
spring-batch copied to clipboard
JobScopeTestExecutionListener invokes methods with JobExecution return type
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
- Create a test with a
JobScopeTestExecutionListener, for example by adding@SpringBatchTest - Add a method with return type
JobExecution, can have any number of arguments
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;
}
}
}
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.
As a temporary workaround I added the following annotation to my test class:
@TestExecutionListeners( listeners = {}, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS )