Question

I have a small Spring app that reads a few databases, and writes a small table in one database. I have a @Service-annotated class with a @Transactional method. That method calls a method in a DAO class (which is not @Repository-annotated) which first deletes some rows from a table and then inserts rows to the same table.

This gets deployed to WebLogic. Under normal operation this app is working perfectly fine.

I tried an experiment of deliberately mucking the SQL for the "insert", and deployed this to my local box, and then executed the JMX operation that executes this service operation. After it failed (expected) I checked the database, and I confirmed that the table was intact, so it correctly rolled back the "delete" when the "insert" failed.

My problem is that my integration test that tries to simulate a similar scenario is NOT behaving transactionally. I mocked the JdbcTemplate so it performed the delete, but forced it to throw a DataAccessException on the insert. Afterwards, I checked the database, and the rows were gone, so it didn't rollback the delete as I hoped.

I turned on debug in the Spring JTA package, and I saw the debug message print that says it was rolling back the transaction.

I'm using the Atomikos transaction manager for my tests. The following is an excerpt from the context I'm using in test to define the "catalogTransactionManager", which is what is referenced in the rest of the context.

<!-- Construct Atomikos UserTransactionManager, needed to configure Spring -->
<bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"
      init-method="init" destroy-method="close">
    <!-- when close is called, should we force transactions to terminate or not? -->
    <property name="forceShutdown">
        <value>true</value>
    </property>
</bean>

<!-- Also use Atomikos UserTransactionImp, needed to configure Spring -->
<bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
    <property name="transactionTimeout">
        <value>300</value>
    </property>
</bean>

<!-- Configure the Spring framework to use JTA transactions from Atomikos -->
<bean id="catalogTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
    <property name="transactionManager">
        <ref bean="atomikosTransactionManager" />
    </property>
    <property name="userTransaction">
        <ref bean="atomikosUserTransaction" />
    </property>
</bean>

It probably doesn't matter, but here's my test method (with some things obfuscated):

@Test
public void testInsertFailsAfterDelete() {
List<ErrorMessageInfo>  commonErrorMessagesBefore   =
    myService.
    getMyDAO().getCommonErrorMessages(MyService.CHANNEL_NAME);

JdbcTemplate  template    = mock(JdbcTemplate.class);
myService.getMyDAO().setJdbcTemplate(template);

when(template.update(eq(MyDAO.SQL_DELETE_CHANNEL), any())).
thenReturn(getOrigTemplate().update(MyDAO.SQL_DELETE_CHANNEL, MyService.CHANNEL_NAME));

DataAccessException    exception   = new DataAccessException("insert failed") {};

when(template.update(eq(MyDAO.SQL_INSERT_ERROR_TO_CHANNEL), anyString(), anyString(), anyInt(), any(), anyInt())).
thenThrow(exception);

try {
myService.updateCommonErrorMessages();
fail();
}
catch (Exception ex) {
assertThat(ex).isEqualTo(exception);
}
finally {
restoreTemplate();
}

List<ErrorMessageInfo>  commonErrorMessagesAfter    =
    myService.
    getMyDAO().getCommonErrorMessages(MyService.CHANNEL_NAME);

assertThat(commonErrorMessagesBefore).isEqualTo(commonErrorMessagesAfter);

}

Note that although when I deploy to WebLogic, I define a normal connection-pooling transactional datasource, but in my integration test, the datasource uses a "org.springframework.jdbc.datasource.DriverManager DataSource".

What might I be missing?

Is it simply because the free Atomikos transaction manager isn't really transactional? What might I be missing?

Was it helpful?

Solution

The problem is with the mock setup. The update on the getOrigTemplate is getting called in the test method not when the update method on the mock is invoked.

when(template.update(eq(MyDAO.SQL_DELETE_CHANNEL), any())).
thenReturn(getOrigTemplate().update(MyDAO.SQL_DELETE_CHANNEL, MyService.CHANNEL_NAME));

you should be doing something like this to get the behavior you want.

when(template.update(eq(MyDAO.SQL_DELETE_CHANNEL), any())).thenAnswer(new Answer() {
     Object answer(InvocationOnMock invocation) {
         return getOrigTemplate().update(MyDAO.SQL_DELETE_CHANNEL,      
                      MyService.CHANNEL_NAME);
     }
 });

OTHER TIPS

I use annotations for making test classes transactional. You may want to refer http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/testing.html#testing-tx for more info .

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top