AnnotatedTestConfiguration.java
/*
*
* The DbUnit Database Testing Framework
* Copyright (C)2002-2026, DbUnit.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package org.dbunit.annotation.runtime;
import java.util.Properties;
import org.dbunit.DatabaseTesterFactory;
import org.dbunit.DefaultPrepAndExpectedTestCase;
import org.dbunit.PrepAndExpectedTestCase;
import org.dbunit.VerifyTableDefinition;
import org.dbunit.annotation.DbUnitConfig;
import org.dbunit.annotation.DbUnitExpected;
import org.dbunit.annotation.DbUnitPrep;
import org.dbunit.annotation.DbUnitRowCountCheck;
import org.dbunit.annotation.DbUnitSetup;
import org.dbunit.annotation.DbUnitTearDown;
import org.dbunit.assertion.FailureHandler;
import org.dbunit.operation.DatabaseOperation;
import org.dbunit.util.fileloader.DataFileLoader;
import org.dbunit.util.fileloader.DataSetPathsProvider;
import org.dbunit.util.fileloader.FileExtensionDataFileLoader;
/**
* Immutable configuration resolved from one test element's {@code org.dbunit.annotation}
* instances, ready for {@link AnnotatedTestExecutor} to drive - no dataset has been applied to
* a database yet, but every dataset path is normalized, every provider and catalog has been
* consulted, and every reflective instantiation that can fail (a provider or comparer with no
* accessible no-arg constructor, conflicting inline-value-and-provider attributes) has already
* happened.
*
* <p>Not intended for direct use by test code; this is machinery consumed by a binding such as
* {@code DbUnitExtension}. Building one is JUnit-free: {@link #from} takes the already-resolved
* annotation instances (method-level, else class-level - a binding's job, since only it knows
* how to search for them) rather than an {@code ExtensionContext}.
*
* @author Jeff Jensen
* @since 3.6.0
*/
public class AnnotatedTestConfiguration
{
private final DataFileLoader dataFileLoader;
private final String[] prepDataFiles;
private final boolean setupDeclared;
private final DatabaseOperation setUpOperation;
private final boolean expected;
private final String[] expectedDataFiles;
private final VerifyTableDefinition[] verifyTableDefinitions;
private final boolean tearDownDeclared;
private final DatabaseOperation tearDownOperation;
private final Properties databaseConfigProperties;
private final FailureHandler failureHandler;
private final boolean closeConnectionAfterTest;
private final Class<? extends DatabaseTesterFactory> databaseTesterFactory;
private final Class<? extends PrepAndExpectedTestCase> prepAndExpectedTestCaseClass;
private final boolean rowCountCheckDeclared;
private final boolean rowCountCheckEnabled;
private final String[] rowCountCheckExclude;
private AnnotatedTestConfiguration(final DataFileLoader dataFileLoader,
final String[] prepDataFiles, final boolean setupDeclared,
final DatabaseOperation setUpOperation, final boolean expected,
final String[] expectedDataFiles,
final VerifyTableDefinition[] verifyTableDefinitions,
final boolean tearDownDeclared, final DatabaseOperation tearDownOperation,
final Properties databaseConfigProperties, final FailureHandler failureHandler,
final boolean closeConnectionAfterTest,
final Class<? extends DatabaseTesterFactory> databaseTesterFactory,
final Class<? extends PrepAndExpectedTestCase> prepAndExpectedTestCaseClass,
final boolean rowCountCheckDeclared, final boolean rowCountCheckEnabled,
final String[] rowCountCheckExclude)
{
this.dataFileLoader = dataFileLoader;
this.prepDataFiles = prepDataFiles;
this.setupDeclared = setupDeclared;
this.setUpOperation = setUpOperation;
this.expected = expected;
this.expectedDataFiles = expectedDataFiles;
this.verifyTableDefinitions = verifyTableDefinitions;
this.tearDownDeclared = tearDownDeclared;
this.tearDownOperation = tearDownOperation;
this.databaseConfigProperties = databaseConfigProperties;
this.failureHandler = failureHandler;
this.closeConnectionAfterTest = closeConnectionAfterTest;
this.databaseTesterFactory = databaseTesterFactory;
this.prepAndExpectedTestCaseClass = prepAndExpectedTestCaseClass;
this.rowCountCheckDeclared = rowCountCheckDeclared;
this.rowCountCheckEnabled = rowCountCheckEnabled;
this.rowCountCheckExclude = rowCountCheckExclude;
}
/**
* Resolves a configuration from one test element's annotation instances.
*
* @param testClass The test class; used to resolve dataset paths relative to its package.
* @param config The resolved {@code @DbUnitConfig}, or {@code null} if absent.
* @param prep The resolved {@code @DbUnitPrep}, or {@code null} if absent.
* @param setup The resolved {@code @DbUnitSetup}, or {@code null} if absent.
* @param expected The resolved {@code @DbUnitExpected}, or {@code null} if absent.
* @param tearDown The resolved {@code @DbUnitTearDown}, or {@code null} if absent.
* @param rowCountCheck The resolved {@code @DbUnitRowCountCheck}, or {@code null} if
* absent.
* @return The resolved configuration.
* @throws IllegalStateException If two mutually exclusive attributes are both set, or if
* a named provider, catalog, or comparer class cannot be instantiated.
*/
public static AnnotatedTestConfiguration from(final Class<?> testClass,
final DbUnitConfig config, final DbUnitPrep prep, final DbUnitSetup setup,
final DbUnitExpected expected, final DbUnitTearDown tearDown,
final DbUnitRowCountCheck rowCountCheck)
{
final String dataSetBaseDir = config == null ? "" : config.dataSetBaseDir();
final DataSetPathsResolver pathsResolver = new DataSetPathsResolver();
final Class<? extends DataFileLoader> dataFileLoaderClass =
config == null ? FileExtensionDataFileLoader.class : config.dataFileLoader();
final DataFileLoader dataFileLoader =
instantiate(dataFileLoaderClass, "DbUnitConfig.dataFileLoader");
final String[] prepDataFiles = pathsResolver.resolve("DbUnitPrep", testClass,
dataSetBaseDir, prep == null ? null : prep.value(),
prep == null ? DataSetPathsProvider.class : prep.provider());
final boolean setupDeclared = setup != null;
final DatabaseOperation setUpOperation = setup == null ? DatabaseOperation.CLEAN_INSERT
: setup.operation().toDatabaseOperation();
final boolean tearDownDeclared = tearDown != null;
final DatabaseOperation tearDownOperation =
tearDown == null ? DatabaseOperation.NONE
: tearDown.operation().toDatabaseOperation();
final boolean hasExpected = expected != null;
final String[] expectedDataFiles = !hasExpected ? new String[0]
: pathsResolver.resolve("DbUnitExpected", testClass, dataSetBaseDir,
expected.value(), expected.provider());
final VerifyTableDefinition[] verifyTableDefinitions = !hasExpected
? new VerifyTableDefinition[0]
: new VerifyTableDefinitionResolver().resolve(config, expected, dataFileLoader,
expectedDataFiles);
final Properties databaseConfigProperties =
new DatabaseConfigPropertiesResolver().resolve(config);
final FailureHandler failureHandler = config == null
|| config.failureHandler() == FailureHandler.class ? null
: instantiate(config.failureHandler(), "DbUnitConfig.failureHandler");
final boolean closeConnectionAfterTest =
config == null || config.closeConnectionAfterTest();
final Class<? extends DatabaseTesterFactory> databaseTesterFactory = config == null
|| config.databaseTesterFactory() == DatabaseTesterFactory.class ? null
: config.databaseTesterFactory();
final Class<? extends PrepAndExpectedTestCase> prepAndExpectedTestCaseClass =
config == null ? DefaultPrepAndExpectedTestCase.class
: config.prepAndExpectedTestCase();
final boolean rowCountCheckDeclared = rowCountCheck != null;
final boolean rowCountCheckEnabled =
rowCountCheckDeclared && rowCountCheck.enabled();
final String[] rowCountCheckExclude =
rowCountCheckDeclared ? rowCountCheck.exclude() : new String[0];
return new AnnotatedTestConfiguration(dataFileLoader, prepDataFiles, setupDeclared,
setUpOperation, hasExpected, expectedDataFiles, verifyTableDefinitions,
tearDownDeclared, tearDownOperation, databaseConfigProperties, failureHandler,
closeConnectionAfterTest, databaseTesterFactory, prepAndExpectedTestCaseClass,
rowCountCheckDeclared, rowCountCheckEnabled, rowCountCheckExclude);
}
private static <T> T instantiate(final Class<? extends T> implementationClass,
final String attributeDescription)
{
return ReflectiveInstantiation.instantiate(implementationClass, attributeDescription);
}
/**
* Returns the {@link DataFileLoader} to use for every dataset file.
*
* @return The data file loader.
*/
public DataFileLoader getDataFileLoader()
{
return dataFileLoader;
}
/**
* Returns the resolved, absolute classpath paths of the prep dataset files.
*
* @return The prep dataset paths; empty when no {@code @DbUnitPrep} was declared.
*/
public String[] getPrepDataFiles()
{
return prepDataFiles.clone();
}
/**
* Returns whether {@code @DbUnitSetup} was declared.
*
* <p>A binding consults this, alongside {@link #getPrepDataFiles()} being non-empty, to
* decide whether to call {@code setSetUpOperation} at all: with neither, the test runs with
* the tester's own pre-configured setup (matching pre-annotation behaviour); with
* {@code @DbUnitSetup} declared but no {@code @DbUnitPrep}, the operation is still applied
* - most usefully {@link org.dbunit.operation.DatabaseOperation#NONE} - to whatever
* dataset the tester already has, rather than leaving a tester with no dataset at all to
* run its unrelated default operation against. Either way a binding driving a tester shared
* across methods should restore the tester's dataset and operations afterward, so a
* per-method {@code @DbUnitSetup}/{@code @DbUnitPrep} does not carry onto a later method.
*
* @return True when {@code @DbUnitSetup} was declared.
*/
public boolean isSetupDeclared()
{
return setupDeclared;
}
/**
* Returns the setup operation.
*
* @return The setup operation; {@link DatabaseOperation#CLEAN_INSERT} when no
* {@code @DbUnitSetup} was declared.
*/
public DatabaseOperation getSetUpOperation()
{
return setUpOperation;
}
/**
* Returns whether {@code @DbUnitExpected} was declared, switching the test onto the
* prep/expected path.
*
* @return True when {@code @DbUnitExpected} was declared.
*/
public boolean isExpected()
{
return expected;
}
/**
* Returns the resolved, absolute classpath paths of the expected dataset files.
*
* @return The expected dataset paths; empty when {@link #isExpected()} is false.
*/
public String[] getExpectedDataFiles()
{
return expectedDataFiles.clone();
}
/**
* Returns the resolved table definitions to verify.
*
* @return The table definitions; empty when {@link #isExpected()} is false.
*/
public VerifyTableDefinition[] getVerifyTableDefinitions()
{
return verifyTableDefinitions.clone();
}
/**
* Returns whether {@code @DbUnitTearDown} was declared.
*
* <p>On the setup/teardown path a binding consults this to decide whether to call
* {@code setTearDownOperation} at all: with no {@code @DbUnitTearDown}, {@code onTearDown()}
* runs with the tester's own pre-configured teardown operation - e.g. one set in a
* {@code @BeforeEach} method - rather than a forced {@link DatabaseOperation#NONE},
* mirroring how an undeclared setup operation is handled (see {@link #isSetupDeclared()}).
* A binding driving a tester shared across methods should restore the operation afterward
* so a per-method {@code @DbUnitTearDown} does not carry onto a later method. On the
* prep/expected path the operation is applied regardless, since {@code cleanupData()}
* always runs there and reads it from the tester lazily.
*
* @return True when {@code @DbUnitTearDown} was declared.
*/
public boolean isTearDownDeclared()
{
return tearDownDeclared;
}
/**
* Returns the teardown operation.
*
* @return The teardown operation; {@link DatabaseOperation#NONE} when no
* {@code @DbUnitTearDown} was declared.
*/
public DatabaseOperation getTearDownOperation()
{
return tearDownOperation;
}
/**
* Returns the resolved {@code DatabaseConfig} properties to apply.
*
* @return The properties; empty when none were declared.
*/
public Properties getDatabaseConfigProperties()
{
final Properties copy = new Properties();
copy.putAll(databaseConfigProperties);
return copy;
}
/**
* Returns the failure handler to use for verification failures.
*
* @return The failure handler, or {@code null} when not set - leaving dbUnit's own
* default in effect.
*/
public FailureHandler getFailureHandler()
{
return failureHandler;
}
/**
* Returns whether the connection this executor resolves - for the prep/expected path, the
* row count check, or parameter injection - is closed after each test.
*
* @return True to close the connection after each test.
*/
public boolean isCloseConnectionAfterTest()
{
return closeConnectionAfterTest;
}
/**
* Returns the {@link DatabaseTesterFactory} implementation to use when no tester field is
* declared.
*
* @return The factory class, or {@code null} when not set.
*/
public Class<? extends DatabaseTesterFactory> getDatabaseTesterFactory()
{
return databaseTesterFactory;
}
/**
* Returns the {@link PrepAndExpectedTestCase} implementation to use for the prep/expected
* path when no test case field is declared.
*
* @return The test case class; {@link DefaultPrepAndExpectedTestCase} when not set.
*/
public Class<? extends PrepAndExpectedTestCase> getPrepAndExpectedTestCaseClass()
{
return prepAndExpectedTestCaseClass;
}
/**
* Returns whether {@code @DbUnitRowCountCheck} was declared.
*
* <p>A binding must consult this before overriding a resolved {@code RowCountCheck}, to
* leave the row count check's own {@code DatabaseConfig}/system-property-based resolution
* untouched when nothing was declared.
*
* @return True when {@code @DbUnitRowCountCheck} was declared.
*/
public boolean isRowCountCheckDeclared()
{
return rowCountCheckDeclared;
}
/**
* Returns the resolved {@code @DbUnitRowCountCheck.enabled()} value.
*
* @return True to enable the check; meaningful only when {@link #isRowCountCheckDeclared()}
* is true.
*/
public boolean isRowCountCheckEnabled()
{
return rowCountCheckEnabled;
}
/**
* Returns the resolved {@code @DbUnitRowCountCheck.exclude()} value.
*
* @return The excluded table name patterns; meaningful only when
* {@link #isRowCountCheckDeclared()} is true.
*/
public String[] getRowCountCheckExclude()
{
return rowCountCheckExclude.clone();
}
}