DBUnit + Hibernate Integration testing

DBUnit is a great integration testing tool for projects that use databases as persintance layer. It is an extension for Junit, which gives all the goodies of JUnit and a powerful mechanism to test your database and persistance services. DBUnit provides easy way to initialise your database, populate initial data and create/restore consistent state for each test.

The purpose of this article is however not to give overview of DBUnit but rather give some basic guidance on how this testing framework could be integrated into projects that are based on hibernate.

So the plan is to make DBUnit use the connection provided by hibernate session to allow testing of the persistence layer services and still make use of the DBUnit's mechanism of database consistent state initialization.

For this purpo we need the following:

1. DBUnit latest release.

2. SLF4J since DBUnit uses it for logging.

3. HSQLDB latest release. (You may use your target database but HSQLDB will lessen the test running time considerably)

so we begin:

1. create a separate hibernate.cfg.xml for testing.

all we need to do is basically copy your existing hibernate.cfg.xml file and change the connection and database properties to use HSQLDB. Here are the properties that you need to adjust:




    
        org.hibernate.dialect.HSQLDialect
        false
        false
        create 
        true 
        org.hsqldb.jdbcDriver
        jdbc:hsqldb:mem:DBNAME
        sa
        
        1

        

        

    

Here is a full sample hibernate config file.

I suppose that you might already have a HibernateUtil class that configures your hibernate session factory but if not here is a basic example:

package com.inspiresoftware.hibernatedbunit.example;

import org.apache.log4j.Logger;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

/**
 * Utility class that provides basics hibernate initialization.
 *
 * @author Denis Pavlov
 * @since 1.0.0
 *
 */

public final class HibernateUtils {

    private static final Logger LOG = Logger.getLogger(HibernateUtils.class);

    /**
     * @param pathToHibernateCfgXml path to where hibernate.test.cfg.xml is located
     *        if file is in /my/dir/with/my.cfg.xml, where name of config file is my.cfg.xml
     *        then all all path with files name must be provided as parameter.
     * @return Hibernate session factory.
     */
    public static SessionFactory newSessionFactory(final String pathToHibernateCfgXml) {
        LOG.info("Loading Hibernate Session Factory with configurations from file "
                + pathToHibernateCfgXml + "...");
        Configuration hibernateConfiguration = new Configuration();
        hibernateConfiguration.configure(pathToHibernateCfgXml);
        return hibernateConfiguration.buildSessionFactory();
    }

}

Example domain object might be as follows:

package com.inspiresoftware.hibernatedbunit.example.domain;

/**
 * .
 * 

* User: denispavlov * Date: May 6, 2012 * Time: 10:20:13 AM */ public class TUserImpl { private Long id; private String username; public Long getId() { return id; } public void setId(final Long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(final String username) { this.username = username; } }

And hibernate mapping looks like this for this class:




    
        
            
        

        
    

2.HSQLDB Server Utility

if you are using HSQLDB you need a utility class that will start up the HSQLDB Server. Here is a sample class:

package com.inspiresoftware.hibernatedbunit.example;

import org.apache.log4j.Logger;
import org.hsqldb.Server;
import org.hsqldb.ServerConfiguration;
import org.hsqldb.persist.HsqlProperties;

/**
 * Utility to start the HSQL server.
 *
 * @author Denis Pavlov
 * @since 1.0.0
 *
 */
public final class HSQLServerUtil {

    private static final Logger LOG = Logger.getLogger(HSQLServerUtil.class);

    private static final HSQLServerUtil UTIL = new HSQLServerUtil();
    private Server hsqlServer;

    private HSQLServerUtil() {
        // prevent instantiation
    }

    /**
     * @return utility instance.
     */
    public static HSQLServerUtil getInstance() {
        return UTIL;
    }

    private void doStart(final HsqlProperties props) {

        ServerConfiguration.translateDefaultDatabaseProperty(props);

        hsqlServer = new Server();
        hsqlServer.setRestartOnShutdown(false);
        hsqlServer.setNoSystemExit(true);
        hsqlServer.setProperties(props);

        LOG.info("Configured the HSQLDB server...");
        hsqlServer.start();
        LOG.info("HSQLDB server started on port " + hsqlServer.getPort() + "...");
    }

    /**
     * start the server with a database configuration.
     * @param dbName the name of database
     * @param port port to listen to
     */
    public void start(final String dbName, final int port) {
        HsqlProperties props = new HsqlProperties();
        props.setProperty("server.port", port);
        props.setProperty("server.database.0", dbName);
        props.setProperty("server.dbname.0", dbName);
        doStart(props);
    }

    /**
     * start the server with a database configuration.
     * @param dbName the name of database
     */
    public void start(final String dbName) {
        HsqlProperties props = new HsqlProperties();
        props.setProperty("server.database.0", dbName);
        props.setProperty("server.dbname.0", dbName);
        doStart(props);
    }

    /**
     * shutdown the started instance.
     */
    public void stop() {
        LOG.info("HSQLDB server shutting down...");
        hsqlServer.stop();
        LOG.info("HSQLDB server shutting down... done");
    }


}

3. Create a base TestCase that will load the hibernate configuration and will start HSQLDB Server.

package com.inspiresoftware.hibernatedbunit.example;

import org.apache.commons.lang.NotImplementedException;
import org.dbunit.DBTestCase;
import org.dbunit.PropertiesBasedJdbcDatabaseTester;
import org.dbunit.dataset.IDataSet;
import org.dbunit.operation.DatabaseOperation;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.junit.After;
import org.junit.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Base DbUnit Test case that initialises test database.
 *
 * @author Denis Pavlov
 * @since 1.0.0
 *
 */
public abstract class HibernateDbUnitTestCase extends DBTestCase {

    private static final Logger LOG = LoggerFactory.getLogger(HibernateDbUnitTestCase.class);

    private static SessionFactory sessionFactory;
    protected Session session;

    /**
     * system properties initializing constructor.
     */
    public HibernateDbUnitTestCase() {
        System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS, "org.hsqldb.jdbcDriver");
        System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_CONNECTION_URL, "jdbc:hsqldb:mem:DBNAME");
        System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_USERNAME, "sa");
        System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_PASSWORD, "");
    }

    /**
     * Start the server.
     * @throws Exception in case of startup failure.
     */
    @Before
    public void setUp() throws Exception {
        HSQLServerUtil.getInstance().start("DBNAME");

        LOG.info("Loading hibernate...");
        if (sessionFactory == null) {
            sessionFactory = HibernateUtils.newSessionFactory("hibernate.test.cfg.xml");
        }

        session = sessionFactory.openSession();

        super.setUp();
    }

    /**
     * shutdown the server.
     * @throws Exception in case of errors.
     */
    @After
    public void tearDown() throws Exception {
        session.close();
        super.tearDown();
        HSQLServerUtil.getInstance().stop();
    }

    /** {@inheritDoc} */
    protected IDataSet getDataSet() throws Exception {
        throw new NotImplementedException("Specify data set for test: " + this.getClass().getSimpleName());
    }

    /** {@inheritDoc} */
    protected DatabaseOperation getSetUpOperation() throws Exception {
        return DatabaseOperation.REFRESH;
    }

    /** {@inheritDoc} */
    protected DatabaseOperation getTearDownOperation() throws Exception {
        return DatabaseOperation.NONE;
    }


}

And Here is a quick example test for User

package com.inspiresoftware.hibernatedbunit.example;

import com.inspiresoftware.hibernatedbunit.example.domain.TUserImpl;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSet;
import org.hibernate.Transaction;
import org.junit.Test;

/**
 * .
 * 

* User: denispavlov * Date: May 6, 2012 * Time: 10:37:37 AM */ public class UsersDbUnitTestCase extends HibernateDbUnitTestCase { /** {@inheritDoc} */ protected IDataSet getDataSet() throws Exception { return new FlatXmlDataSet(this.getClass().getResourceAsStream("/test/integration/data/users.xml")); } /** * Demo test to see that the number of user records in the database corresponds the flat file inserts. */ @Test public void testDemo1() { Transaction trans = session.beginTransaction(); assertEquals(4, session.createCriteria(TUserImpl.class).list().size()); trans.commit(); } /** * Demo test to see that the number of user records in the database corresponds the flat file inserts. */ @Test public void testDemo2() { Transaction trans = session.beginTransaction(); assertEquals("bob", ((TUserImpl) session.get(TUserImpl.class, 2L)).getUsername()); trans.commit(); } }

with simple flat xml test file:



                                                                   


                                                                    

So a quick overview:

1. In the constructor we define the system properties that dbUnit will use for connection.

2. In the #setUp() we start the server, initialize the session factory (which will recreate the database using hbm2dll) and call the super.setUp() of DBTestCase which will load the initial data using the #getDataSet() method which will define the data set to use for this test suite. You probably will need to define a different data set for each of test suite's but it is placed here for the demo.

3. In the #tearDown() we close the hibernate session run the DBUnit stuff by calling super.tearDown() and shutdown the hsqldb server.

4. Notice the getSetUpOperation() and getTearDownOperation() these two are instructions for the DBUnit on how to prepare the database for the current test. REFRESH means that we need to recreate all data from scratch before we run the test and NONE means that we want to do nothing after the test is run (The none in after this makes no sense for the HSQLDB but if you use some other database this will allow you to view the data after the test has been run, so that you can see what is there for your self)

Some additional notes: probably it is not a good idea to start and shutdown the HSQLDB server each test but this is just a demo, so you probably will need to do some additional thinking on how you want your integration test to run.

Now all we need to do is to extend our HibernateDbUnitTestCase class that write some tests. As you can see what we do is that is the constructor of the test case we start the HSQLDB Server and initialise the hibernate sesstion factory. Then we open the session and provide connection for DBUnit.

There is a special hook method that is this test case. The purpose of it is to initialise the fresh database that hibernate had created for us with data. And this is were DUnit gives us a great help by loading the data from flat XML files. So that all test that we write may provide their own data that is needed just for the test.

Finally in finalize method we close all our connections and shutdown the HSQLDB server.

Hope you enjoyed this tutorial and it will help you in your work.

Full source code for this tutorial is here

This page was last updated on: 06/05/2012 06:33