Help

Built with Seam

You can find the full source code for this website in the Seam package in the directory /examples/wiki. It is licensed under the LGPL.

You are probably already familiar with the magic import.sql file which, when present in the root of your classpath, will be executed by Hibernate automatically on application startup. You can see an example of this in the any seam-gen'erated application skeleton in the /resources/import-*.sql files.

This feature was never intended to be used for more than very basic testing and it is quite limited. There is no support for different databases, and SQL statements in this file are separated by newlines. Which means that for anything more complex than a few SQL statements, maintaining this file would be impractical.

Seam already supports importing mock data in automated unit tests based on more powerful DBUnit datasets (see reference documentation).

In development, you often also need mock data for deployment on your development machine(s). It is very convenient to use the same dataset files both for unit testing and development deployment. This page describes how you can automatically import mock data whenever you deploy your application, and how you can use the same datasets you already use for automated unit testing.

Requirements

The following code extends a base class that is only available in Seam version 2.2.1 or newer. Although that base class is also available in earlier Seam versions, this extension will not work with earlier version. (At the time of writing, Seam 2.2.1 was not released. Use a preview release or nightly build.)

The implementation has only been tested with a seam-gen'erated application skeleton. All configuration and deployment settings shown are based on a seam-gen skeleton. If your application has not been produced by seam-gen, or you made significant modifications, configuration is probably going to be different.

Implementation

We need a Seam component that imports DBUnit dataset files on application startup. The component is not installed by default but has to be configured through components.xml - you do not want to execute this component in a production environment or in unit tests, only in development mode.

Create the package common.persistence (or any other package you'd like to use) and drop the following class into that package:

package common.persistence;

import org.dbunit.operation.DatabaseOperation;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Install;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Observer;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.core.Events;
import org.jboss.seam.log.Log;

import java.util.ArrayList;
import java.util.List;

/**
 * Imports some data into the database with the help of DBUnit. This allows us to
 * use the same dataset files as in unit testing, but in the regular application startup during
 * development. Also helps to avoid maintaining the crude Hibernate import.sql file for development
 * deployment.
 *
 * @author Christian Bauer
 */
@Name("dbunitImporter")
@Scope(ScopeType.APPLICATION)
@Install(value = false)
public class DBUnitImporter extends DBUnitSeamTest {

    public static final String IMPORT_COMPLETE_EVENT = "DBUnitImporter.importComplete";

    @Logger
    static Log log;

    protected List<String> datasets = new ArrayList<String>();

    public List<String> getDatasets() {
        return datasets;
    }

    public void setDatasets(List<String> datasets) {
        this.datasets = datasets;
    }

    protected void prepareDBUnitOperations() {
        if (datasets == null) return;

        for (String dataset : datasets) {
            log.info("Adding DBUnit dataset to import: " + dataset);
            beforeTestOperations.add(
                new DataSetOperation(dataset, DatabaseOperation.CLEAN_INSERT)
            );
        }
    }

    // Do it when the application starts (but after everything else has been loaded, esp. the persistence unit)
    @Observer("org.jboss.seam.postInitialization")
    @Override
    public void prepareDataBeforeTest() {
        log.info("Importing DBUnit datasets using datasource JNDI name: " + getDatasourceJndiName());
        super.prepareDataBeforeTest();
        Events.instance().raiseEvent(IMPORT_COMPLETE_EVENT);
    }

}

We re-use the DBUnitSeamTest class and let it do all the heavy lifting for us. This class is usually only extended for automated unit tests, but there is no reason why you shouldn't use it in development mode as well.

The tricky part is the actual import of the datasets. It has to occur after the persistence context (Hibernate) has started, because we need the schema to be present in the database before we import data - which Hibernate will do for us in development mode with the hibernate.hbm2ddl.auto property in persistence.xml set to 'create' or 'create-drop'. So we execute the import of the datasets when Seam has finished initializing, when we are sure that either a container- or Seam-started persistence unit has been executed.

Configuration

Enable the component in WEB-INF/components.xml as follows:

<component class="common.persistence.DBUnitImporter" installed="@testDataImport@">
    <property name="database">MYSQL</property>
    <property name="datasourceJndiName">@testDataImportDatasource@</property>
    <property name="binaryDir">META-INF/testfiles</property>
    <property name="datasets">
        <value>META-INF/testdata/ProductData.dbunit.xml</value>
    </property>
</component>

This snippet uses placeholders, as we want to be able to configure it further via components.properties which we can easily control for different deployment profiles (dev, prod, test) in a seam-gen application build.

We also need to copy the datasets (both DBUnit XML files and test binaries such as images) to the classpath on deployment, so they are available in META-INF/testdata and META-INF/testfiles. You can configure a different path here, however, they are always resolved against the classpath. Of course you could also deploy your test datasets and files in a separate JAR.

For a seam-gen skeleton, add the following settings to components-dev.properties

testDataImport=true
testDataImportDatasource=@datasourceJndiName@

and change this filterset in build.xml to finally set the datasource name:

<filterset id="seam">
    ...
    <filter token="datasourceJndiName" value="${project.name}Datasource"/>
</filterset>

Don't forget to copy your test data into the deployed classpath, for example add this to the war target:

<!-- DBunit Import -->
<copy todir="${war.dir}/WEB-INF/classes/META-INF/testdata" flatten="true">
    <fileset dir="${src.test.dir}">
        <include name="**/*.dbunit.xml"/>
    </fileset>
</copy>
<copy todir="${war.dir}/WEB-INF/classes/META-INF/testfiles">
    <fileset dir="${src.testfiles.dir}"/>
</copy>

Finally, you now need dbunit.jar in your classpath when the application is deployed (at least in development mode, not for production). In a seam-gen skeleton, add it as a dependency to deployed-jars.list.