8

I have Spring Batch job that writes to the database (it has a step with a JpaItemWriter). I have an integration test such as follows:

@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("integrationTest")
public class LoadApplicationTests {

    @Autowired
    private Job job;

    @Autowired
    private JobRepository jobRepository;

    @Autowired
    private JobLauncher jobLauncher;

    private JobLauncherTestUtils jobLauncherTestUtils;

    @Before
    public void setUp() throws IOException, java.text.ParseException, Exception {       
        jobLauncherTestUtils = new JobLauncherTestUtils();
        jobLauncherTestUtils.setJob(job);
        jobRepository = new MapJobRepositoryFactoryBean(new ResourcelessTransactionManager()).getObject();
        jobLauncherTestUtils.setJobRepository(jobRepository);
        jobLauncherTestUtils.setJobLauncher(jobLauncher);
    }

    @Test
    public void testJob() throws Exception {
        JobParametersBuilder j = new JobParametersBuilder();
        JobParameters jobParameters = j.addDate("runDate", new Date())
                .addString("file", testFile.getAbsolutePath())
                .addString("override", "false")
                .addString("weekly", "false")
                .toJobParameters();

        JobExecution jobExecution = jobLauncherTestUtils.launchJob(jobParameters);

        Assert.assertEquals("COMPLETED", jobExecution.getExitStatus().getExitCode());
    }
}

When running the job in the test, it commits to the database. How can I prevent committing to the database? Normally, I could add @Transactional to rollback the transaction after each test. However, when I add the annotation the test class, I receive:

java.lang.IllegalStateException: Existing transaction detected in JobRepository. Please fix this and try again (e.g. remove @Transactional annotations from client).

Update

I have tried to add @Rollback to the test class. However, the JpaItemWriter still commits.

Here's the configuration for the transaction manager in the application code:

@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(entityManagerFactory);
    return transactionManager;
}

@Bean
public Step stepLoadFile(StepBuilderFactory stepBuilderFactory, 
        PlatformTransactionManager transactionManager,
        ItemReader<MyClass> reader, ItemProcessor<MyClass, 
        MyClass> processor, 
        ItemWriter<MyClass> writer, 
        ReadFailureHandler readListenerSupport,
        WriteFailureHandler writeListenerSupport) {
    Step step = stepBuilderFactory.get("stepPersistFile")
            .transactionManager(transactionManager)
            .<MyClass, MyClass> chunk(1000)      
            .reader(reader)
            .processor(processor)
            .listener(writeListenerSupport)
            .listener(readListenerSupport)
            .writer(writer)
            .build();

    return step;
}
Patrick
  • 10,322
  • 12
  • 63
  • 100
James
  • 3,524
  • 14
  • 59
  • 95
  • I assume that you have checked that your datasource/connection is NOT in autocommit mode? – Hansjoerg Wingeier Sep 22 '16 at 11:18
  • Thanks for the comment. For the same datasrouce/connection I have repository tests annotated with `@Transactional`. These tests save to the DB and are rolled back after the end of each test. So, it seems that it is not in autocommit mode. I wonder if Spring Batch is committing the transaction in the call to `launchJob`. So, the test has nothing to rollback because it is already committed in the call to `launchJob`. – James Sep 22 '16 at 14:30

4 Answers4

1

To overcome this my group has simply written an @After hook to clear the data that was written. It is not pretty, nor desired, but it appears to be getting us through our issues.

Keep in mind, that this will still write your job executions to batch_job_execution, batch_job_execution_context, etc.

Also recognize, that you'll probably want to ensure your spring.batch.job.enabled should be set to false in your test/resources/application.[properties|yml].

Fun times ‍♂️

Dan
  • 1,837
  • 16
  • 21
1

@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) may be what you are looking for, and is what I have used in the past when testing programs which alter database records. Based on the Spring Docs, @DirtiesContext is a

Test annotation which indicates that the ApplicationContext associated with a test is dirty and should therefore be closed and removed from the context cache. Use this annotation if a test has modified the context — for example, by modifying the state of a singleton bean, modifying the state of an embedded database, etc. Subsequent tests that request the same context will be supplied a new context.

Otherwise, building off of @Dan's answer, TestTransaction may allow for more explicit control of your test transactions within methods annotated with @Test, @Before, and @After. See here for more info.

darkmnemic
  • 200
  • 2
  • 9
0

I suppose you have transactionManager, if so, add

@TransactionConfiguration(transactionManager="transactionManager", defaultRollback=true)

at the top of your test class.

Asoub
  • 1,986
  • 1
  • 14
  • 27
  • Thanks for the answer. I'm using the `JpaTransactionManager` for the batch job. I'm using Spring boot 1.4.0.RELEASE which gives me the Spring Framework 4.3.2.RELEASE. In that version, `@TransactionConfiguration` is deprecated. I tried your suggestion anyway. The error is prevented, but `JpaItemWriter` commits to the database. – James Sep 21 '16 at 17:13
  • Mh, have you tried @ Rollback ? IF @transactionConfiguration doesn't work, I'm not usre this will, but still.. https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/annotation/Rollback.html – Asoub Sep 22 '16 at 06:48
  • Thanks. I did try@Rollback but it didn't work. See Update section in OP. – James Sep 22 '16 at 14:18
0

Normally the strategy I use for integration tests is have a profile (as you have) with Embedded DB creation for the integration tests, with a specific application-test.properties so then the impact of them are more controlled when you are concerned about data modification and these kind of stuffs.

Regarding your case, it looks that you are in the right path adding the @Transactional for tests purposes, as we can see on this topic. Then I would invest my time investigating further about the exception you get when this error happens.

java.lang.IllegalStateException: Existing transaction detected in JobRepository. Please fix this and try again (e.g. remove @Transactional annotations from client).

Also this exception looks something quite known, so I would recommend to take a look on this other topic to figure out what was tried. They had pretty much the same issue and wrote an article about how to avoid this error happening (also on this thread).

Another possibility commented is upgrade spring code version, because in some cases this issues started happening right after the upgrade.

Now, talking more about the components used, you look using spring-batch, which has its particular suite to test the batch slices and etc, so I would recommend you to take a look on how to use/test spring-batch and the spring-batch test documentation itself. Maybe using the components provided by spring-boot for batch testing there are pre-made decisions and strategies that mitigate your issue.

Eduardo Meneses
  • 473
  • 3
  • 16