Friday 3 August 2007

Spring Framework Integration Tests

Having been a huge fan of the Spring framework, and esp. the JDBC support within it, I have had a shocking two days working on the most simple of tests. I wrote a simple Repository / DAO to pull out a four parameter javabean from a four column table, providing two finder methods to query for an object by two of the four parameters.

This is the ABC of database access, and code that most of our team can write in our sleep. But the problem came not with the code, but attempting to TEST the code.

The obvious choice for testing Spring JDBC Repository code is the famously named AbstractTransactionalDataSourceSpringContextTests class. This provides a transaction to work in, so all changes are wiped between tests, as well as a convenient JdbcTemplate object configured in your Spring context to point to the database. All in all this allowed me to write a quick test to wipe the table I was interested in clean, insert one row, then use the Repository I was testing to pull back that one row and confirm the object wasn't null.

And it failed... so I tweaked and fixed and refactored and guessed and shouted. But it still failed. I got down to just running two lines of code in my test:

jdbcTemplate.update("Insert into CLIENTS (ID, DESCRIPTION) VALUES (1, 'TEST'");
assertTrue(jdbcTemplate.queryForInt("SELECT count(0) from CLIENTS ") > 0);

And... it failed !?! This led to others (convinced of my stupidity) chipping in with ideas like checking the output of the deletes and inserts - they returned the numbers of rows affected as expected. But it seemed that EVERY SQL statement ran seemed to have no effect on the others - as if the transaction might be rolling back between every single statement ! I could insert the same row with the same primary key twice, and each time it told me it had added a row, and each time a select statement bought back 0 rows.

This was frustrating - the "my first SQL for dummies" step in Spring JDBC and it had me grinding my teeth and swearing under my breath, not to mention impacting on a tight deadline (what other kind are there ?!) and raising the stress levels - I needed to step back. Something wasn't right with my transaction manager, but what could it be...

First step - got in with the debugger and look what I've setup. My test case has the jdbcTemplate setup with the same DataSource as the TransactionManager. Next step - go through the code. It seemed that all the Jdbc statements were running with numberOfTransactions set to 0 and transactionStatus set to null - clearly no transaction was working around the code here ? But equally a transactionManager MUST be set up, otherwise the test case would not actually start (the spring code throws an exception).

Some more investigation and delving into the spring source code, and suddenly I see my error...

I have overridden onSetUp(). With the spring transactional test cases, the setUp() method is final, so onSetUp() is provided. But this method is non-empty in AbstractTransactionalSpringContextTests, and it is here that all the transactional magic happens. And of course, in overriding it, I had not thought to call super.onSetUp(), so stopped all of the transactional set up happening. I can only assume the side effect of every statement not affecting the other was caused by autoCommit being set to false on the connection as well.

The lesson here - make sure to call the super.onSetUp() method (a good rule for all Junit set up methods, not just this example. Or more specific to this problem, use the provided onSetUpBeforeTransaction() and onSetUpInTransaction() methods which are empty place holder methods for subclasses to add behaviour. This quick change applied and everything works normally and makes sense again. Finally my brain works and I can get onto some new code :)

No comments: