Search code examples
springtransactionsspring-test-dbunit

Spring integration test with manual transaction management


I've read many posts and threads about integration testing with Spring but nothing is either satisfying or helpful.

We're using Spring 3.2.3 with Hibernate, Spring Data and an Oracle database. For testing we also use DbUnit and Spring-test-dbunit. In production code, the transaction is started by a controller, the service itself does not know anything about a transaction.

So, here is my test:

@ContextConfiguration // ...
@ActiveProfiles // ...
@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({
    DependencyInjectionTestExecutionListener.class,
    DirtiesContextTestExecutionListener.class,
    TransactionalTestExecutionListener.class,
    ModifiedDbUnitTestExecutionListener.class })
@DbUnitConfiguration(databaseConnection = "oracleConnection")
@DatabaseSetup("/database/snapshot/snapshot.xml")
public class IntegrationTest extends AbstractTransactionalJUnit4SpringContextTests
{
    @Test
    public void sampleTest()
    {
        // transaction is already started

        this.assertThatNewsContains(0);

        News news1 = new News();
        news1.setTitle("Test News 1");
        News savedNews1 = this.newsService.save(news1);
        Assert.assertTrue(savedNews1.getId() > 0);

        News news2 = new News();
        news2.setTitle("Test News 2");
        News savedNews2 = this.newsService.save(news2);
        Assert.assertTrue(savedNews2.getId() > 0);

        News news3 = new News();
        news3.setTitle("Test News 3");
        News savedNews3 = this.newsService.save(news3);
        Assert.assertTrue(savedNews3.getId() > 0);

        // transaction commit should occur HERE
        // @todo: HOW ?!

        this.assertThatNewsContains(3);
    }

    private void assertThatNewsContains(int newsSize)
    {
        List<News> allNews = this.newsService.getNews();
        Assert.assertEquals(newsSize, allNews.size());
    }

}

What I found out is that, if I annotate the NewsService with @Transactional(propagation=Propagation.REQUIRES_NEW) the test works fine, however it is not the same as in production mode. @Transactional(propagation=Propagation.REQUIRED) is not sufficient as the DbUnit-Spring-test opens a transaction by itself and the latter assert fails as the transaction is not committed yet. How can I achieve that a transaction is committed BEFORE the last assert is executed?


Solution

  • I finally managed to execute some code in a separate transaction.

    You need

    @Autowired
    private PlatformTransactionManager          platformTransactionManager;
    
    private TransactionTemplate             transactionTemplate;
    

    in your test class. Then you can do the following:

    this.transactionTemplate = new TransactionTemplate(this.platformTransactionManager);
    
    // note that parameters passed to the transaction must be final!
    final Object parameter = something;
    
    Object returnedValue = this.transactionTemplate.execute(new TransactionCallback<Object>()
        {
            @Override
            public Object doInTransaction(TransactionStatus status)
            {
                return doSomethingAndReturnAnObject(parameter);
            }
        }
    );