AbstractDbTest.java
package com.reallifedeveloper.tools.test.database.dbunit;
import java.io.IOException;
import java.util.Arrays;
import java.util.Optional;
import javax.sql.DataSource;
import org.dbunit.dataset.DataSetException;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.datatype.IDataTypeFactory;
import org.dbunit.ext.mssql.InsertIdentityOperation;
import org.dbunit.operation.DatabaseOperation;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.transaction.annotation.Transactional;
/**
* Base class for DBUnit tests.
* <p>
* An example of how to use this class:
*
* <pre>
* @ExtendWith(SpringExtension.class)
* @ContextConfiguration(classes = TestConfiguration.class)
* public class MyDbTest extends AbstractDbTest {
* @Autowired
* private DataSource ds;
*
* @Autowired
* private IDataTypeFactory dataTypeFactory;
*
* public MyDbTest() {
* super(null, "/dbunit/myschema.dtd", "/dbunit/dataset1.xml", "/dbunit/dataset2.xml");
* }
*
* @Test
* public void myTest() {
* ...
* }
*
* @Override
* protected DataSource getDataSource() { return ds; }
*
* @Override
* protected IDataTypeFactory getDataTypeFactory() { return dataTypeFactory; }
* }
* </pre>
*
* @author RealLifeDeveloper
*
*/
@Transactional
public abstract class AbstractDbTest {
/**
* The default operation to perform before executing each test.
*/
public static final DatabaseOperation DEFAULT_SETUP_OPERATION = InsertIdentityOperation.CLEAN_INSERT;
/**
* The default operation to perform after executing each test.
*/
public static final DatabaseOperation DEFAULT_TEARDOWN_OPERATION = DatabaseOperation.NONE;
private String schemaName;
private String dataSetDtdResourceName;
private String[] dataSetResourceNames;
private DbTestHelper dbTestHelper;
/**
* Creates a new test instance, using the given schema and reading the DBUnit XML file found in the given classpath resource.
*
* @param schemaName name of the database schema, or {@code null}
* @param dataSetResourceName name of the classpath resource that contains DBUnit XML
*/
protected AbstractDbTest(String schemaName, String dataSetResourceName) {
this(schemaName, null, dataSetResourceName);
}
/**
* Creates a new test instance, using the given schema and reading the DBUnit XML files and DTD found in the given classpath resources.
*
* @param schemaName name of the database schema, or {@code null}
* @param dataSetDtdResourceName name of the classpath resource containing the DTD for the XML, or {@code null} to not validate
* @param dataSetResourceNames names of classpath resources containing DBUnit XML
*/
protected AbstractDbTest(String schemaName, String dataSetDtdResourceName, String... dataSetResourceNames) {
if (dataSetResourceNames == null) {
throw new IllegalArgumentException("dataSetResourceName must not be null");
}
this.schemaName = schemaName;
this.dataSetDtdResourceName = dataSetDtdResourceName;
this.dataSetResourceNames = dataSetResourceNames.clone();
}
/**
* Called before each test case to insert test data into the database.
*
* @throws Exception if something goes wrong
*/
@BeforeEach
@SuppressWarnings("PMD.SignatureDeclareThrowsException")
public void setUpDatabase() throws Exception {
dbTestHelper = new DbTestHelper(getDataSource(), getDataSet(), getSchemaName(), getDataTypeFactory());
dbTestHelper.setSetUpOperation(getSetUpOperation());
dbTestHelper.setTearDownOperation(getTearDownOperation());
dbTestHelper.init();
}
/**
* Called after each test case to clean the database from test data.
*
* @throws Exception if something goes wrong
*/
@AfterEach
@SuppressWarnings("PMD.SignatureDeclareThrowsException")
public void tearDownDatabase() throws Exception {
if (dbTestHelper != null) {
dbTestHelper.clean();
}
}
/**
* Gives the name of the database schema. Can be {@code null}.
*
* @return the database schema name
*/
protected String getSchemaName() {
return schemaName;
}
/**
* Gives the name of the resource containing the DTD for test data files.
*
* @return the name of the DTD resource
*/
protected String getDataSetDtdResourceName() {
return dataSetDtdResourceName;
}
/**
* Gives the names of the resources containing test data.
*
* @return the names of the test data resources
*/
protected String[] getDataSetResourceNames() {
return Arrays.copyOf(dataSetResourceNames, dataSetResourceNames.length);
}
/**
* Gives the data set used by DbUnit when testing. The default implementation reads XML files from the classpath resources pointed to by
* the {@code dataSetResourceNames} property, validating using the DTD pointed to by the {@code dataSetDtdResourceName} property.
* <p>
* Override this method if you want to provide the DbUnit test data in some other way.
*
* @return the test data set
*
* @throws DataSetException if some resource if malformed
* @throws IOException if reading a resource failed
*/
protected IDataSet getDataSet() throws DataSetException, IOException {
return DbTestHelper.readDataSetFromClasspath(getDataSetDtdResourceName(), getDataSetResourceNames());
}
/**
* Override this to point to your datasource. The datasource could, e.g., be injected by Spring.
*
* @return the {@code DataSource} to use
*/
protected abstract DataSource getDataSource();
/**
* Gives the DbUnit {@code IDataTypeFactory} that matches the database used for testing, or {@code null} if the database type is left
* unspecified. If left unspecified, DbUnit will probably issue a lot of warnings about "potential problems".
* <p>
* The default implementation returns {@code null}. Override this to return the right {@code IDataTypeFactory} for your database, e.g.,
* {@code org.dbunit.ext.mysql.MySqlDataTypeFactory} or {@code org.dbunit.ext.oracle.Oracle10DataTypeFactory}.
* <p>
* To make it easy to change to use a different database, the factory could be configured by Spring and injected into your test case.
*
* @return the{@code IDataTypeFactory} to use
*/
protected Optional<IDataTypeFactory> getDataTypeFactory() {
return Optional.empty();
}
/**
* Gives the operation to perform before executing each test.
* <p>
* The default is {@link #DEFAULT_SETUP_OPERATION}. Override this method to change this behavior.
*
* @return the setup operation to perform
*/
protected DatabaseOperation getSetUpOperation() {
return DEFAULT_SETUP_OPERATION;
}
/**
* Gives the operation to perform after executing each test.
* <p>
* The default is {@link #DEFAULT_TEARDOWN_OPERATION}. Override this method to change this behavior.
*
* @return the setup operation to perform
*/
protected DatabaseOperation getTearDownOperation() {
return DEFAULT_TEARDOWN_OPERATION;
}
/**
* Make finalize method final to avoid "Finalizer attacks" and corresponding SpotBugs warning (CT_CONSTRUCTOR_THROW).
*
* @see <a href="https://wiki.sei.cmu.edu/confluence/display/java/OBJ11-J.+Be+wary+of+letting+constructors+throw+exceptions">
* Explanation of finalizer attack</a>
*/
@Override
@SuppressWarnings({ "checkstyle:NoFinalizer", "PMD.EmptyFinalizer", "PMD.EmptyMethodInAbstractClassShouldBeAbstract" })
protected final void finalize() throws Throwable {
// Do nothing
}
}