Rollback DB Changes in Unit Tests with Spring

Posted On // 1 comment
Observation 1: Applications are written human beings, which tend to make mistakes. It's a good idea to have some tools in place to help catch those mistakes early by validating assumptions against reality. One such tool is unit testing.

Observation 2: Almost every application out there needs access to some kind of persistent data storage. In a lot of application code, that storage mechanism is some kind of relational database.

It is easy to understand, based on the situations described above, that unit testing tools should take into account handling initializing persistent storage systems properly for testing. Thankfully, JUnit has some extensions to make unit testing with a particular set of data in the relational DB easier.

DBUnit provides a way to represent test relational data in an XML form that is human readable, and can be loaded into most any JDBC data source. DBUnit's strategy is to reset the database to a known good base state before each unit test runs. If your set of test data is small this strategy works just fine, but as your test data grows you have to think more about segmenting your data to keep your unit test runs from taking too long. There is a strategy you can take to allow you to keep your larger data set, and yet still maintain some level of isolation between your test cases. You can leverage the transactional capabilities of your relational database to make sure that changes in one unit test don't affect the results of other unit tests.

The concept basically involves running DBUnit once before unit tests to make sure that the database is in a good state, and then running each test case in its own transaction. Finally, at the end of the test case, you roll back the transaction to make sure that any changes the test case made to the relational database aren't committed. If you happen to be using the Spring Framework as the basis for your application, then there are some handy features to make this task easier.

Many frameworks have moved to the use of Java Annotations to make use of a particular framework easier. JUnit 4 introduced the ability for a developer to simply annotate methods in a class with @Test to have that method called by the test runner. The Spring Framework has been introducing the use of annotations for configuration since the 1.2 version, but much of the framework functionality wasn't exposed through annotations until the 2.5 version.

One of the earliest annotation capabilities added to the Spring Framework was the ability to annotation a method or class with @Transactional. This annotation along with some other configuration settings told the framework to automatically handle creating, committing, and rolling back of transactions.

One of the features added in the 2.5 version of the framework was the ability to specify a default transaction configuration for unit tests. This configuration is useful for specifying an alternative transaction manager for unit tests (if you need a special one to run outside a container), but it also contains a setting called "defaultRollback" which allows you to tell the test runner what to do with transactions that were started by the framework for a test.

The combination of the previous two features allows one to simply annotate a class containing test methods to have all it's database operations rolled back at the end of each test. To use the feature, you will need to be using version 2.5 of the Spring Framework:

//Imports and other stuff removed...

//This annotation tells JUnit to use Spring's test runner to run this test.
//This is a good thing if you want to use other Spring features without
//polluting your test code.
@RunWith(SpringJUnit4ClassRunner.class)
//This annotation tells Spring that we want each method in this class to be
//executed within it's own transaction. You can also apply this annotation
//selectively to each method that needs a transaction, and have other
//methods execute outside a transaction.
@Transactional
//This annotation tells the Spring test runner to always roll back each
//transaction it creates.
@TransactionConfiguration(defaultRollback=true)
public class SimpleSpringTransactionalTest
{
//Private variables, other tests...

//Standard JUnit test case annotation
@Test
public void someTestCode()
{
//Do something that modifies the DB...
//Make some assertions...
}
//After this test case executes, all the DB changes will be rolled back
//indiscriminately

//Other tests and code...
}

1 comments:

Nick said...

I was unable to get this working until I also added the following to the test class...


@TestExecutionListeners({TransactionalTestExecutionListener.class})