21 December 2011

Spring Integration Tests with Real Transactions

Spring's Test Context has a @Transactional annotation for wrapping tests in a transaction started and rolled back by the test container, to keep the tests from modifying the database. This is just what you need for deterministic, repeatable database tests.

On the other hand, due to this approach, the transaction boundaries in your test system differ from the ones in your production system, which can lead to errors in production which were never noticed in your tests suites.

I wouldn't go as far as saying that transactional tests should be considered harmful, but at least you should be aware of the side effects which may or may not be harmless in your specific use case.

For tests with real transactions, you need to take care of cleaning up the database yourself.

JUnit rules are a neat way of doing this. Here is an example:


Using the CleanDatabaseRule


@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration("/META-INF/spring/test-context.xml") 
public class RealTransactionTest { 

    @Rule 
    @Inject 
    public CleanDatabaseRule cleanDatabase; 

    @Inject
    private UserDao userDao;

    @Test 
    public void testFindUsers() { 
        userDao.createUsers("wilma", secret); 
        assertThat(userDao.findAllUsers().size(), is(1));
    } 
} 

This test class has the usual @RunWith and @ContextConfiguration annotations, to enable the Spring Test Context, but the @Transactional annotation is missing

The CleanDatabaseRule is marked with @Rule. JUnit requires all rule members to be public. Given that our rule internally works with a persistence unit to be injected by Spring, the rule itself is configured as a singleton Spring bean to be injected into our test class.

This rule takes care of deleting all database content before and after each test method.

CleanDatabaseRule Implementation


Our CleanDatabaseRule extends JUnit's ExternalResourceRule which has before() and after() methods for dealing with a given external resource (the database in our case) before and after running a test method.

The level of cleanup performed by this rule is up to you: you can drop the database and create a new one, you can restore a given database dump, or simply truncate all tables, which is what I'm doing here.

The following example uses MySQL syntax to disable all foreign keys before deleting the tables:

public class CleanDatabaseRule extends ExternalResource { 

    @PersistenceUnit 
    private EntityManagerFactory emf; 


    @Override 
    protected void before() { 
        EntityManager em = emf.createEntityManager(); 
        em.getTransaction().begin(); 

        // disable foreign keys
        em.createNativeQuery("SET FOREIGN_KEY_CHECKS = 0").executeUpdate(); 
        truncateTables(em); 

        // reenable foreign keys 
        em.createNativeQuery("SET FOREIGN_KEY_CHECKS = 1").executeUpdate(); 

        em.getTransaction().commit(); 
   }

    @Override 
    protected void after() { 
        before();
    }
} 

Note that we're injecting an EntityManagerFactory via @PersistenceUnit instead of the usual EntityManager This is because the entity managers injected by Spring do not permit manual transactions. Using an EntityManagerFactory, we can create our own EntityManager and control or own transactions.

In a database schema with lots of foreign key constraints, it is useful to disable them while cleaning up, to avoid the headache of having to delete tables in the correct order.

The truncateTables() method is left as an exercise. :-)

3 comments:

Goahead said...

Man your post is so great!, Just was I was searching!

Chris Wood said...

This was very helpful. Thanks!

Maksim said...

Brilliant post!