I answer this more for my own reference, but here it goes. The answer assumes a SQL Server database for each developer.
Basic approach
Use DBUnit to store an XML file of known state. You can extract this file after you have configured the database, or you can create it from scratch. Put this file in your version control along with the scripts that invoke DBUnit to populate the database with it.
In your tests, call the above scripts using @Before
.
Acceleration 1
Once this works, set up an approach to speed things up. Here is an approach to a SQL Server database.
Before DBUnit completely destroy the database:
EXEC sp_msforeachtable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'; EXEC sp_MSforeachtable 'ALTER TABLE ? DISABLE TRIGGER ALL'; EXEC sp_MSForEachTable 'SET QUOTED_IDENTIFIER ON SET ANSI_NULLS ON DELETE FROM ?';
After DBUnit, Restore Constraints
EXEC sp_MSforeachtable 'ALTER TABLE ? CHECK CONSTRAINT ALL'; EXEC sp_MSforeachtable 'ALTER TABLE ? ENABLE TRIGGER ALL';
Acceleration 2
Use SQL Server RESTORE functionality. In my tests, this runs at 25% of the time that DBUnit takes. If (and only if) this is an important factor in the duration of the test, you should study this approach.
The following classes show implementation using Spring JDBC, JTDS, and CDI injections. This is intended to work in tests inside the container, where the container can create its own database connections that need to be stopped.
import java.io.File; import java.sql.SQLException; import javax.inject.Inject; import javax.sql.DataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jdbc.core.JdbcTemplate; @SuppressWarnings({ "PMD.SignatureDeclareThrowsException" }) public abstract class DbResetterSO { protected final Logger logger = LoggerFactory.getLogger(getClass()); private final File backupFile = new File( "target\\test-classes\\db-backup.bak"); @Inject private OtherDbConnections otherDbConnections; public void backup(final DataSource masterDataSource) throws Exception { final JdbcTemplate masterJdbcTemplate = new JdbcTemplate( masterDataSource); if (backupFile.exists()) { logger.debug("File {} already exists, not backing up", backupFile); } else { otherDbConnections.start(); setupDbWithDbUnit(); otherDbConnections.stop(); logger.debug("Backing up"); masterJdbcTemplate.execute("BACKUP DATABASE [" + getDbName() + "] TO DISK ='" + backupFile.getAbsolutePath() + "'"); logger.debug("Finished backing up"); otherDbConnections.start(); } } public void restore(final DataSource masterDataSource) throws SQLException { final JdbcTemplate masterJdbcTemplate = new JdbcTemplate( masterDataSource); if (!backupFile.exists()) { throw new IllegalStateException(backupFile.getAbsolutePath() + " must have been created already"); } otherDbConnections.stop(); logger.debug("Setting to single user"); masterJdbcTemplate.execute("ALTER DATABASE [" + getDbName() + "] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;"); logger.info("Restoring"); masterJdbcTemplate.execute("RESTORE DATABASE [" + getDbName() + "] FROM DISK ='" + backupFile.getAbsolutePath() + "' WITH REPLACE"); logger.debug("Setting to multi user"); masterJdbcTemplate.execute("ALTER DATABASE [" + getDbName() + "] SET MULTI_USER;"); otherDbConnections.start(); } protected abstract String getDbName(); protected abstract void setupDbWithDbUnit() throws Exception; } import java.sql.SQLException; public interface OtherDbConnections { void start() throws SQLException; void stop() throws SQLException; } import java.sql.Connection; import java.sql.SQLException; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.enterprise.inject.Produces; import javax.inject.Named; import javax.inject.Singleton; import javax.sql.DataSource; import net.sourceforge.jtds.jdbcx.JtdsDataSource; import org.springframework.jdbc.datasource.DelegatingDataSource; import org.springframework.jdbc.datasource.SingleConnectionDataSource; @Singleton @SuppressWarnings({ "PMD.AvoidUsingVolatile" }) public abstract class ResettableDataSourceProviderSO implements OtherDbConnections { private volatile Connection connection; private volatile SingleConnectionDataSource scds; private final DelegatingDataSource dgds = new DelegatingDataSource(); @Produces @Named("in-container-ds") public DataSource resettableDatasource() throws SQLException { return dgds; } @Override @PostConstruct public void start() throws SQLException { final JtdsDataSource ds = new JtdsDataSource(); ds.setServerName("localhost"); ds.setDatabaseName(dbName()); connection = ds.getConnection(username(), password()); scds = new SingleConnectionDataSource(connection, true); dgds.setTargetDataSource(scds); } protected abstract String password(); protected abstract String username(); protected abstract String dbName(); @Override @PreDestroy public void stop() throws SQLException { if (null != connection) { scds.destroy(); connection.close(); } } }
source share