Realsolve Logo
Blogs
 

Phil Zoio's Weblog, August 26, 2005

Adapting JUnit libraries to work with TestNG

The fact that there are so many JUnit libraries about may be considered by many a barrier to adopting TestNG, but I've discovered how easy it is to adapt JUnit libraries to work with TestNG.

Take DbUnit as an example. DbUnit is a database testing library which adds value primarily by making it easy to define and load test data sets (from a variety of sources) used for testing database operations. For more info see here. JUnit has a very simple life cycle, in fact painfully so. It was this fact that led TestNG to be set up in the first place!

DbUnit has adapted this life cycle contract to allow you to any combination of adding, inserting or deleting from a defined data set during the setUp() and tearDown() calls. Typically, before each test method is run, you clean the database state, then insert the data from the test data set.

I'm working on a test framework for Hibernate applications, for which I also want to use DbUnit. The problem is that the Hibernate part does not fit so neatly into this lifecycle. The main thing Hibernate setup task is the creation of the SessionFactory. This takes quite a bit of time (up to a few seconds), so it's not what you want to do before and after every test method executes. (JUnit tests from the framework itself get around this by creating the SessionFactory in a static initialization block. Pretty ugly.

TestNG by contrast allows you to configure your test configuration lifecycle to fit the needs of your test scenario. For my Hibernate tests, I want initialization of the SessionFactory to happen before any tests in a class are run, and DbUnit database operations to run before individual test methods. TestNG allows this.

Also, with JUnit there is another problem, perhaps worse. JUnit forces you to subclass TestCase (or some subclass of this). In my situation, this would not present any practical problems if I were only working only with DbUnit. However, to add the Hibernate specific configuration, I need to create a subclass of DatabaseTest case (DbUnit's JUnit subclass), and add the SessionFactory initialization functionality in there. Before even attempting to do anything complicated, the class becomes messy.

TestNG allows you to break free of these issues. Simply create an adapter for all the JUnit-based test libraries which you wish to use. The implementation is surprisingly simple - I simply add setter methods for the DbUnit test case properties that I need to override. My DbUnit adapter looks like this:

public class DbUnitTest extends DatabaseTestCase
{

    private IDataSet dataSet;

    private DatabaseOperation setupOperation;

    private DatabaseOperation teardownOperation;

    private ConnectionProperties connectionProperties;


    public IDatabaseConnection getConnection() throws Exception
    {
        return DbUnitUtils.createConnection(this.connectionProperties);
    }

    /* *************** public setter methods *************** */

    public void setDataSet(IDataSet dataSet)
    {
        this.dataSet = dataSet;
    }

    public void setSetupOperation(DatabaseOperation setupOperation)
    {
        this.setupOperation = setupOperation;
    }

    public void setTeardownOperation(DatabaseOperation teardownOperation)
    {
        this.teardownOperation = teardownOperation;
    }
   
    public void setConnectionProperties(ConnectionProperties connectionProperties)
    {
        this.connectionProperties = connectionProperties;
    }

    @Override
    protected IDataSet getDataSet() throws Exception
    {
        return this.dataSet;
    }

    @Override
    protected DatabaseOperation getSetUpOperation() throws Exception
    {
        if (setupOperation == null) return super.getSetUpOperation();
        return setupOperation;
    }

    @Override
    protected DatabaseOperation getTearDownOperation() throws Exception
    {
        if (teardownOperation == null) return super.getTearDownOperation();
        return teardownOperation;
    }

    /* ********** JUnit test lifecycle methods ********** */

    public void setUp() throws Exception
    {
        super.setUp();
        if (connectionProperties == null)
            throw new IllegalStateException("Cannot call configure as connectionProperties property is null");
    }

    public void tearDown() throws Exception
    {
        setupOperation = null;
        teardownOperation = null;
        super.tearDown();
    }
}

Based on the possibility that I may need to use my Hibernate test functionality as a JUnit test, I have also followed the JUnit lifecycle contract in writing the Hibernate test case. Again, my implementation of this is much simpler than if I were trying to mix in DbUnit functionality.

With TestNG I can now configure the DbUnit and Hibernate elements of my test class independently.

public class TestNGHiberTest extends BaseTest
{

    private HibernateSupportTest hibernateTest;
    private DbUnitTest dbUnitTest;

    @Configuration(beforeTestClass = true)
    protected void configureHibernate() throws Exception
    {
        hibernateTest = new HibernateSupportTest();
        hibernateTest.setConfigurationBuilder(new FileConfigurationBuilder(FileUtils
                .getDatabaseFile("hibernate/hibernate.cfg.xml")));
        hibernateTest.setUp();
    }

    @Configuration(afterTestClass = true)
    public void unconfigureHibernate() throws Exception
    {
        hibernateTest.tearDown();
    }
    
    @Configuration(beforeTestMethod = true)
    protected void configureDbUnit() throws Exception
    {
        dbUnitTest = new DbUnitTest();
        dbUnitTest.setDataSet(null);
        dbUnitTest.setSetupOperation(DatabaseOperation.NONE);
        dbUnitTest.setTeardownOperation(DatabaseOperation.NONE);
        
        dbUnitTest.setConnectionProperties(new ConnectionProperties("com.mysql.jdbc.Driver", "jdbc:mysql://leahpar/test",
                "phil", "phil"));
    }   
    
    @Configuration(afterTestClass = true)
    public void unconfigureDbUnit() throws Exception
    {
        hibernateTest.unconfigure();
    }
    
    @Configuration(beforeTestMethod = true)
    protected void configureMethod() throws Exception
    {
        DataUpdateUtils.runUpdate("delete from project", SpringUtils.getDataSource());
        hibernateTest.getHibernateSupport().rollbackTransactionSafely();
    }

    @Test
    public void test1() throws Exception
    {
        HiberTestOperation p = new BaseHiberTestOperation()
        {
            public void test(Session session)
            {
                Project project = new Project();
                project.setProjectId(10L);
                project.setProjectName("My project name 1");
                session.save(project);
            }
        };
        new TestOperationExecutor(hibernateTest.getHibernateSupport(), dbUnitTest.getConnection()).runTest(p);
    }

    @Test
    public void test2() throws Exception
    {
        HiberTestOperation p = new BaseHiberTestOperation()
        {
            public void test(Session session)
            {
                Project project = new Project();
                project.setProjectId(10L);
                project.setProjectName("My project name 2");
                session.save(project);
            }
        };
        new TestOperationExecutor(hibernateTest.getHibernateSupport(), dbUnitTest.getConnection()).runTest(p);
    }

}

There we can see that the Hibernate and DbUnit elements of the test are configured separately according to different life cycles. If I wanted to add some functionality from XMLUnit or any other JUnit extension, I could follow a similar procedure and configure my test with little difficulty.

It would be nice to see adapters written for all the major JUnit-based libaries so that they could easily be used within TestNG tests in this way.





Want to comment on this blog? Please email me at philzoio@realsolve.co.uk. At some stage I'll probably add a form, or maybe even a public forum, but for now I'm keeping the site pretty basic.