Wednesday, June 17, 2009

Spring Testing & Transaction Manager

Transaction is one of the topic that always baffles me. Spring framework is supposed to take a bit of this burden away from the programmer. But a lot of time we (programmers) take this for granted, without looking into details under the hood.
I'm a classical example of this type of programmer.

I'm currently working on a project, which heavily uses Spring framework. We use spring within the application and for testing.

i.e.:
our code looks something like:


@Service
@Transactional
public class SomeServiceImpl implements SomeService{
public void someTransactionalMethod(){
...
}
}


and the spring config looks like:




xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
">
...




...



This is all fine within the code itself. The transaction manager spring bean is named 'hbTransactionManager', and we explicitly says that transactional annotation is to be managed by this transaction manager instance.

Unfortunately, sometime during the project, we added LDAP components to the project,
and no-surprise, we decided to use Spring LDAP. It happens (somehow) that we introduced Spring LDAP Transaction manager:























Note that this transaction manager uses 'transactionManager' as the bean identifier.

The code seems to be working fine for now - but I cant guarantee that anymore. Anyway, what I wanted to talk about, is the test codes.
Our base test class looks like:


@ContextConfiguration(locations = {"classpath:applicationContextTest-common.xml"})
public abstract BaseTestCase extends AbstractTransactionalJUnit4SpringContextTests{
...
}


while test cases that extends this class uses Spring LDAP's transaction manager, it is not the case with some other test cases that extends another abstract class:


@TransactionConfiguration(transactionManager = "hbTransactionManager")
public abstract BaseIntegrationTestCase extends BaseTestCase {
}


This only came to my attention in my recent attempt to write a test case that test the rollback behaviour within our codes:




public class SomeTestThatShouldFail extends BaseIntegrationTestCase{

@Autowired
private SomeService service;

@Test
public void test_something(){
..
try{
service.someTransactionalMethod(); // i expect this to fail
Assert.assertFail("Shouldnt succeed");
}catch(Exception e){
}
...
// do some verification that rollback occurs
}
}


The test case above failed. Because of the following:
1. The test case uses 'hbTransactionManager'.
2. The code uses transactional annotation, which is specified to use the same transaction manager (hbTransactionManager).
3. Since the test case start the transaction, the failed operation wont be rolled back at the end of method invocation (because the transaction wasnt started at the point of method invocation!)
4. Therefore my verification for rollback wont succeed. The rollback will be performed at the end of the test.


The fix is to change the code above to:


@TransactionConfiguration
public class SomeTestThatShouldFail extends BaseIntegrationTestCase{

@Autowired
private SomeService service;

@Test
public void test_something(){
..
try{
service.someTransactionalMethod(); // i expect this to fail
Assert.assertFail("Shouldnt succeed");
}catch(Exception e){
}
...
// do some verification that rollback occurs
}
}


This time, the test case will use the default transaction manager - which is the Spring LDAP's transaction manager. The rollback will occur at the end of the service.someTransactionMethod() invocation, because it was started there.


Lesson learnt - never take things for granted. Always look at what happens under the hood.

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]

<< Home