DbUnit

I’ve decided to revisit the JUnit testing Hibernate and Spring recipe that I posted a while back. A problem with the previous recipe is that it did not provide any means to initialize the test database. This wasn’t too much of a problem as I was mostly testing the data insert operations of the DAOs. I then used the same DAO to retrieve the newly inserted data and tested what came back. However this is no good if I don’t want insert operations on my DAO (if it’s to retrieve read only data from the database) or if I want to test the retrieval operations independently of the insert operations.

This post extends the recipe to include a means of initialising the database using DbUnit.

In its own words:

DbUnit is a JUnit extension (also usable with Ant) targeted at database-driven projects that, among other things, puts your database into a known state between test runs.

In addition, I’ve upgraded my test class to use JUnit 4 style annotations rather than extending (a subclass of) TestCase as was required in JUnit 3.

JUnit 4 and Spring TestContext Framework

The Spring TestContext Framework introduced in Spring 2.5 provides an annotation based alternative to the clunky AbstractSingleSpringContextTests class available in Spring 2.0. It can be used by subclassing one of the JUnit 3 / JUnit 4 / TestNG abstract support classes. In the case of JUnit 4.4 it can also be used within a custom JUnit Runner eliminating the need for your test class to subclass anything.

Using either of these methods, your Spring context can be specified using the @ContextConfiguration annotation. I’ve used the custom JUnit Runner so my class definition looks like this:

With this minimal preamble in place I can now inject my Spring beans directly into instance variables using the @Autowired annotation:

Note that I use @Qualifier to specify the name of my DAO as I have two beans in my Context with type SpannersDAO.

DbUnit setUp and tests

DbUnit provides a good quick Getting Started guide. It can either be configured by subclassing DBTestCase (which itself is a subclass of JUnit 3 TestCase) or by using an instance of IDatabaseTester. As I’ve opted to follow a JUnit 4 annotation based style, I don’t want to subclass TestCase so I create my own instance of IDatabaseTester. In my case I want to create a DatabaseTester from a DataSource (which I’ve already injected via Springy magic) so I use the DataSourceDatabaseTester. In my setUp method I want to instantiate the DatabaseTester, provide it with a data set to insert into my database and then tell it to initialize my database by calling onSetup():

The pre-populated database can now be queried via the DAOs:

Source

Download source of this demo

This demo is available as a zip file. After unzipping, the test can be run using Maven 2:

5 Comments

  • Pingback: JUnit testing Hibernate and Spring « Don't Panic!

  • December 3, 2010 - 2:00 pm | Permalink

    FYI: the guys over at Carbon 5 created a custom annotation-driven TestExecutionListener for DbUnit that can be used with the Spring TestContext Framework. So I recommend you take a look at it: http://blog.carbonfive.com/2008/07/testing/database-testing-with-spring-25-and-dbunit

    Regards,

    Sam (author of the Spring TestContext Framework 😉 )

  • Pingback: Test Coverage « Don't Panic!

  • July 30, 2012 - 10:24 am | Permalink

    Hi, I’m experimenting with DBUnit as well. Before that I just use Spring Unit/Integration Test and using a DataGenerationService to create sample data through Hibernate. For example, if I want to deactivate a user, first I create that user in the test initialization, then check the state before and after the deletion is performed. It’s kind of over-simplication, but hope you get the idea.

    When I apply DBUnit, I had the problem about how to adapt it effectively to an existent project. I mean, I can make a simple DBUnit version, but I don’t see much benefits from them. The data state would be much more clearer in XML style; but I must specify all the IDs (student id 1, id 2, …) – which can be ignored in Hibernate; and must match the foreign key by hand. It’s ok if the data is small, but my project has about nearly 90 tables… and the structure are regularly changed to fit the requirement.

    That brings up a lot of work when we add some new field, modify a constraint… and so on. With a Hibernate data generation utility it would be simpler (several lines of code to assign value to the new property in a loop, for example)

    But maybe just because I did it the wrong way. I’m not quite familiar with DBUnit yet, so I seek your advices… Could you please share me some of your experiences?

  • July 31, 2012 - 9:53 pm | Permalink

    Hand coding DBUnit xml data sets can be fiddly, particularly if you want to represent complex data structure or a large data set. A couple of general points regarding this:

    1. Good modular unit tests would tend to test a single table and its relationships and not the whole database at the same time. I would usually create one xml file to represent each table and have my test load only the xml files it needs. You may be able to reuse xml data sets across multiple tests.

    2. Simple unit tests would tend to use a minimal subset of test data rather than a whole production data set. Each xml file may only need a few rows of data to be the basis of a useful unit test.

    That said, hand coding 90 xml files – even minimal ones – will be time consuming so you may want to consider scripting an existing data set using Jailer as described in another post: http://www.disasterarea.co.uk/blog/?p=462

    Regarding fixing your unit tests for a constantly changing database structure, I’m sorry, I don’t think there is an easy way to manage that. If you maintain a test database and then use a tool like Jailer to export it (this can be automated into the build process), you’ll always have up to date DBUnit test data. But you will most likely find that many of your old tests fail and will have to be fixed manually.

  • Leave a Reply

    Your email address will not be published. Required fields are marked *