Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JUnit multiple transaction managers for spring tests

I'm using Spring 3.0.5 and Junit 4.8.2 Is it possible to use multiple transaction managers during tests?

Basically I'm try for something like this. I need to add and remove content from two separate databases during the tests.

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = { "classpath:/applicationContext-test.xml" })
    @TransactionConfiguration(transactionManager = "txMgrA", defaultRollback = true)
    @TransactionConfiguration(transactionManager = "txMgrB", defaultRollback = true)
    @Transactional 
public class SampleTest {
    ...
}
like image 749
Croydon Dias Avatar asked Jun 24 '11 06:06

Croydon Dias


2 Answers

Since Java will not allow multiple annotations of the same type per element, you must find another way to configure it. @TransactionConfiguration is interpreted by TransactionalTestExecutionListener, whose getTransactionManager method only returns a single PlatformTransactionManager. It looks at @Transactional but seems to ignore the value qualifier that was added in Seam 3.0.

@Transactional itself only supports a single transaction manager. How is the real application configured? You must be using @Transactional("<qualifier>") (as in the docs), right?

If you just use @Transactional with different tx managers on different methods, then the simplest solution is to just split your test class.

Are you nesting the transactions? That is, you have @Transactional("tm1") on one method, which calls a nested method that has @Transactional("tm2")? Sounds a little unusual. You could try to set up your test in the same way -- have two test @Services, each with the appropriate @Transactional annotations, that are proxied with tx:advice as usual. The outer service sets up the outer txn; the inner service sets up the inner txn and contains the actual test code. You can't use @Rollback, but hey, hacks ain't pretty.

Another option would be to create your own PlatformTransactionManager that delegates to the two other managers (for testing purposes only).

Maybe better would be to just give up and manually manage the two transactions in the test's @Before/@After methods.

Best would be to use JTA global transactions. Hopefully you're not actually nesting separate transactions and this is all moot ;)

like image 91
Peter Davis Avatar answered Sep 21 '22 11:09

Peter Davis


Looking at Spring 5 TransactionalTestExecutionListener implementation, it looks it only supports one TransactionManager per thread, what seems to be a design flaw of this listener, probably the same as you came across in 2011 :)

However, currently it's possible to workaround this using ChainedTransactionManager. If you have multiple transaction managers, you can define yet another transaction manager in your test context:

@Configuration
class TestTransactionConfig {

    @Bean("testTransactionManager")
    public PlatformTransactionManager chainedTransactionManager(
        @Qualifier("transactionManager1") PlatformTransactionManager transactionManager1,
        @Qualifier("transactionManager2") PlatformTransactionManager transactionManager2
    ) {
        return new ChainedTransactionManager(transactionManager1, transactionManager2);
    }

}

Now, you can define your base class for tests using this transaction manager:

@RunWith(SpringRunner::class)
@Transactional("testTransactionManager")
class BaseTransactionalTest {

}

For all derived classes all test methods will now be wrapped in both transactions, which will be finally rolled back by TransactionalTestExecutionListener.

like image 39
Lukasz Frankowski Avatar answered Sep 17 '22 11:09

Lukasz Frankowski