Spring integration test with manual transaction management
Asked Answered
P

2

7

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?

Proceed answered 10/8, 2015 at 8:23 Comment(0)
P
8

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);
        }
    }
);
Proceed answered 20/12, 2016 at 16:2 Comment(1)
Even simpler with lambdas: Object returnedValue = this.transactionTemplate.execute(s -> doSomethingAndReturnAnObject(parameter));Rains
C
0

As mentioned in the documentation

http://springtestdbunit.github.io/spring-test-dbunit/

If you have configured DBUnit tests to run using the are DbUnitTestExecutionListener and are also using the TransactionalTestExecutionListener you may experience problems with transactions not being started before your data is setup, or being rolled back before expected results can be verified. In order to support @Transactional tests with DBUnit you should use the TransactionDbUnitTestExecutionListener class.

And to initiate transaction you have to annotate your test method or class with @Transactional annotation like this.

@Test
@Transactional
    public void sampleTest()
    {
Culinary answered 10/8, 2015 at 8:48 Comment(4)
Thanks for your reply but it's not about the @ Transactional. It's about commiting a transaction manually AND DIRECTLY AFTERWARDS assert that the database is an an expected state. The @ Transactional is not necessary as the database is resetted before each test.Proceed
OK may be you need to handle manually commit by flushing the session as you are already inside a global test transaction of dbunit ? SessionFactory.getCurrentSession.flush()Culinary
Or moviing the saving part into a new testSave() method by wrapping the method by a Transactional(requires_new) , may be it will commit after leaving the method ?Culinary
No success so far. Kept googling: maybe @PersistenceContext(type=PersistenceContextType.EXTENDED) private EntityManager entityManager; is be expedient?Proceed

© 2022 - 2024 — McMap. All rights reserved.