PrepAndExpectedTestCase

Overview

PrepAndExpectedTestCase, and its default implementation DefaultPrepAndExpectedTestCase, is a test case formally supporting prep data and expected data concepts and definitions.

Prep data
is the setup data needed in the database for the test to run.
Expected data
is the data to compare with one or more database tables verifying the test ran successfully.

PrepAndExpectedTestCase easily allows defining which are the setup datasets (prep) and the verify datasets (expected).

It conveniently packages a turn-key test setup and verification process in one:

  1. Loads the prep dataset files and inserts their data into the database tables
  2. Runs the test steps (if specified as a runTest() PrepAndExpectedTestCaseSteps parameter)
  3. Verifies table state matches the expected datasets for the specified VerifyTableDefinitions
  4. Cleans up the tables listed in the prep and expected datasets (per the configured teardown operation)

What This Automates For You

Compared to hand-coding the same setup/verify/cleanup logic yourself — e.g. driving an IDatabaseTester directly, or a raw JDBC-based test — DefaultPrepAndExpectedTestCase automatically handles several things that would otherwise need to be written by hand for every test:

Multi-file dataset composition
Each of the prep and expected data file arrays is loaded and merged into a single dataset for you (CompositeDataSet under the hood, combining same-named tables found across multiple files) — no manual CompositeDataSet construction needed just to split prep data across several files by table.
Row ordering
Both the actual (database) and expected tables are automatically wrapped in a SortedTable (sorted using each column’s real data type, not string comparison) before they are compared. You never need a manual ORDER BY or to wrap tables in SortedTable yourself to make row order predictable. By default the actual table sorts by all of its native columns and the expected table by only the columns its file declares; for a table whose generated/identity column is excluded from comparison and whose insertion order isn’t guaranteed (e.g. Hibernate batch inserts), set VerifyTableDefinition.sortOnFilteredColumnsOnly to true so both tables sort by only their filtered columns instead.
Column type/case reconciliation
If the expected dataset’s columns come back with an UNKNOWN data type (common for a FlatXmlDataSet with no DTD), the expected table’s column definitions are derived from the actual database table’s columns instead — matched case-insensitively (Locale.ENGLISH, so a Turkish default locale can’t break matching) — so your expected data file’s column name casing doesn’t need to exactly match the database’s, and you don’t need to hand-declare column types.
Column include/exclude filtering
A VerifyTableDefinition's column inclusion/exclusion filters are applied to both the actual and expected tables for you via DefaultColumnFilter — see Filters for the underlying mechanism you’d otherwise wire up by hand.
ValueComparer dispatch
A VerifyTableDefinition's default and per-column ValueComparers are dispatched automatically per column during comparison (see ValueComparer Comparison) — you configure the map once on the VerifyTableDefinition instead of calling Assertion.assertWithValueComparer() yourself for each table.
Expected-table/VerifyTableDefinition count check
Before verifying, it confirms every table in the expected dataset has a corresponding VerifyTableDefinition, failing fast on a forgotten one instead of silently skipping verification for that table — see the property to relax this if a mismatch is intentional.
Setup/cleanup operations
The configured setup and teardown DatabaseOperations (default CLEAN_INSERT / NONE) run automatically around your test steps — no manual onSetup()/onTearDown() calls needed when using runTest().

Verifying Your Teardown Table List Is Complete

The teardown operation only cleans the tables named in your prep and expected datasets. Two mistakes here are silent at the point they happen and only surface later, in an unrelated test: forgetting to list a table the code under test writes to (its rows survive teardown), and wrongly listing a reference table that should never be cleaned (teardown strips rows the DDL seeded).

RowCountCheck is an opt-in diagnostic for exactly this: it compares every table’s row count before preTest() and after cleanupData(), failing the test by name when a count moved instead of letting the corruption surface in whichever test happens to run next. It is off by default; turn it on with DatabaseConfig.FEATURE_ROW_COUNT_CHECK (or -Ddbunit.rowCountCheck=true) when verifying a suite’s teardown correctness, and see the RowCountCheck page for what it catches, how to exclude legitimately-changing tables, and how to read a failure. On a DefaultPrepAndExpectedTestCase instance, setRowCountCheckOverride(enabled, exclude) sets both directly instead - see Configuring DatabaseConfig Without Subclassing below.

Usage

Configure

Configure this class in one of two ways:

  1. Dependency inject it as its interface into a test class.
    @Inject
    private PrepAndExpectedTestCase testCase;

    Create it by configuring an instance of its interface (start with DefaultPrepAndExpectedTestCase and extend & override if necessary), injecting a IDatabaseTester and a DataFileLoader using the databaseTester and a dataFileLoader properties (see Configuration Example Using Spring below).

  2. Extend it in a test class. Obtain IDatabaseTester and DataFileLoader instances (possibly dependency injecting them into the test class) and set them accordingly, probably in a setup type of method, such as:
    @BeforeEach
    public void setDbunitTestDependencies()
    {
        setDatabaseTester(databaseTester);
        setDataFileLoader(dataFileLoader);
    }

Run Tests

PrepAndExpectedTestCase has two ways to setup, execute, and clean up tests:

  1. Encapsulate the test steps in PrepAndExpectedTestCaseSteps and call the runTest() method. Note, this requires Release 2.5.2 and newer.
  2. Call the configureTest(), preTest(), and postTest() methods. Note there is a preTest() convenience method that takes the same parameters as the configureTest() method; use it instead of using both configureTest() and preTest(). Where the test calls those methods depends on data needs:
    • For the whole test case, i.e. in setUp() and tearDown() or @Before and @After.
    • In each test method.
    • Or some combination of both test case setup/teardown and test methods.

(see Test Examples below)

Annotation-Driven Equivalent

On JUnit 5/6 (Jupiter), the configureTest()/preTest()/postTest() block above is formulaic enough that DbUnitExtension can run it declaratively instead, via org.dbunit.annotation:

@DbUnitTest
class AccountRepositoryTest
{
    IDatabaseTester databaseTester;

    AccountRepositoryTest() throws ClassNotFoundException
    {
        databaseTester =
                new JdbcDatabaseTester("org.h2.Driver", "jdbc:h2:mem:example;DB_CLOSE_DELAY=-1");
    }

    @Test
    @DbUnitPrep("/dbunit/accounts/prep.xml")
    @DbUnitExpected(value = "/dbunit/accounts/expected.xml", verifyTables = "ACCOUNT")
    @DbUnitTearDown(operation = DbUnitOperation.DELETE_ALL)
    void testWithdraw_sufficientBalance_decrementsBalance()
    {
        // code under test
    }
}

This covers the common, fixed-per-method case; see DbUnit Annotations for the full reference, and its When Not to Use These Annotations section for the @ParameterizedTest and non-dbUnit-assertion cases where the programmatic API above remains the right tool. Bundling Prep, Expected, and Verify Into One Argument below shows how to keep that API compact in a @ParameterizedTest.

Bundling Prep, Expected, and Verify Into One Argument

configureTest(), preTest(), and runTest() each take the same three arguments in the same order — a VerifyTableDefinition array, a prep data file String array, and an expected data file String array. The three describe one test scenario and are meaningless apart.

PrepAndExpectedTestData bundles them into one immutable value, and each of those three methods has an overload accepting it. In a single-scenario test this saves little. In a data-driven test it stops the triple from propagating through the @ParameterizedTest method signature, every row of the @MethodSource, and any row-factory method written to keep those rows readable.

Two shortcuts cover the common partial cases: PrepAndExpectedTestData.NONE is a shared constant that verifies nothing and loads nothing — replacing a separate empty constant per array type — and PrepAndExpectedTestData.prepOnly(String…​) builds one that loads prep data with no verification.

Before

private static final VerifyTableDefinition[] VERIFY_TABLES_SUCCESS =
        {AppVerifyTables.ACCOUNT, AppVerifyTables.TRANSACTION};
private static final VerifyTableDefinition[] VERIFY_TABLES_NONE = {};
private static final String[] PREP_FILES_SUCCESS = {PREP_DIR + "accounts.xml"};
private static final String[] PREP_FILES_NONE = {};
private static final String[] EXPECTED_FILES_SUCCESS = {EXPECTED_DIR + "accounts-after.xml"};
private static final String[] EXPECTED_FILES_NONE = {};

@ParameterizedTest(name = "[{index}] {0}")
@MethodSource("testData")
void test(String testName, VerifyTableDefinition[] verifyTables, String[] prepDataFiles,
        String[] expectedDataFiles, BigDecimal amount, HttpStatus expectedStatus)
        throws Exception
{
    testCase.runTest(verifyTables, prepDataFiles, expectedDataFiles, () -> {
        restTester.post(WITHDRAW_URL, amount, expectedStatus);
        return null;
    });
}

private static Object[][] testData()
{
    return new Object[][] {
            {"sufficient balance", VERIFY_TABLES_SUCCESS, PREP_FILES_SUCCESS,
                    EXPECTED_FILES_SUCCESS, AMOUNT_VALID, HttpStatus.OK},
            {"insufficient balance", VERIFY_TABLES_NONE, PREP_FILES_NONE,
                    EXPECTED_FILES_NONE, AMOUNT_TOO_LARGE, HttpStatus.BAD_REQUEST},
    };
}

After

private static final PrepAndExpectedTestData DATA_SUCCESS = new PrepAndExpectedTestData(
        new VerifyTableDefinition[] {AppVerifyTables.ACCOUNT, AppVerifyTables.TRANSACTION},
        new String[] {PREP_DIR + "accounts.xml"},
        new String[] {EXPECTED_DIR + "accounts-after.xml"});

@ParameterizedTest(name = "[{index}] {0}")
@MethodSource("testData")
void test(String testName, PrepAndExpectedTestData testData, BigDecimal amount,
        HttpStatus expectedStatus) throws Exception
{
    testCase.runTest(testData, () -> {
        restTester.post(WITHDRAW_URL, amount, expectedStatus);
        return null;
    });
}

private static Object[][] testData()
{
    return new Object[][] {
            {"sufficient balance", DATA_SUCCESS, AMOUNT_VALID, HttpStatus.OK},
            {"insufficient balance", PrepAndExpectedTestData.NONE, AMOUNT_TOO_LARGE,
                    HttpStatus.BAD_REQUEST},
    };
}

Six constants collapse to one, three method parameters to one, and three columns per @MethodSource row to one. Each scenario’s three facts now sit together instead of being matched by position across three arrays.

Configuring DatabaseConfig Without Subclassing

Some tests need to tune the shared connection’s DatabaseConfig - its batch size, IDataTypeFactory, or the RowCountCheck enabled flag and excluded tables, for example. The general-purpose way is extending DefaultPrepAndExpectedTestCase and overriding setUpDatabaseConfig(DatabaseConfig), as shown in Configuration Example Using Spring below - needed whenever a value requires constructor arguments or setup beyond a no-arg constructor.

For everything else, two setters configure the same kind of values without a subclass:

setDatabaseConfigProperties(Properties)
Sets one or more DatabaseConfig values by property name (long or short form) and string value, the same conversion setPropertiesByString() gives Ant/Maven-driven configuration. This covers String, Boolean, comma-separated String[], and Integer properties directly, and any object-typed property (IDataTypeFactory, IMetadataHandler, etc.) whose implementation class has a public no-arg constructor, by instantiating it reflectively from the class name.
Properties properties = new Properties();
properties.setProperty("batchedStatements", "true");
properties.setProperty("datatypeFactory", "com.example.XxxDataTypeFactory");

PrepAndExpectedTestCase testCase =
        new DefaultPrepAndExpectedTestCase(dataFileLoader, databaseTester);
testCase.setDatabaseConfigProperties(properties);
setRowCountCheckOverride(boolean, String[]) / clearRowCountCheckOverride()
Enables or disables RowCountCheck and sets its excluded table patterns directly, instead of setting FEATURE_ROW_COUNT_CHECK/PROPERTY_ROW_COUNT_CHECK_EXCLUDE_TABLES via setUpDatabaseConfig().
testCase.setRowCountCheckOverride(true, new String[] {"AUDIT_LOG"});

Call clearRowCountCheckOverride() to return to resolving the check from the connection’s own DatabaseConfig. This only matters when one instance is reused across more than one test (e.g. a JUnit 5/6 @DbUnitTestCase static field - see Annotation-Driven Equivalent above) - otherwise an earlier test’s override would silently carry over onto a later test that declares none.

Configuration Example Using Spring

The following configuration shows customizing DatabaseConfig and enables dependency injecting the created PrepAndExpectedTestCase. See Configuring DatabaseConfig Without Subclassing above for simpler alternatives to the setUpDatabaseConfig() override below, when Spring-managed construction and the full subclass aren’t otherwise needed.

@Configuration
@Validated
public class DbUnitConfiguration
{
    /**
     * Extend DefaultPrepAndExpectedTestCase to customize DatabaseConfig.
     */
    private class MyPrepAndExpectedTestCase
            extends DefaultPrepAndExpectedTestCase
    {
        public MyPrepAndExpectedTestCase(
                final DataFileLoader dataFileLoader,
                final IDatabaseTester databaseTester)
        {
            super(dataFileLoader, databaseTester);
        }

        @Override
        protected void setUpDatabaseConfig(final DatabaseConfig config)
        {
            // set properties as needed

            config.setProperty(DatabaseConfig.FEATURE_BATCHED_STATEMENTS, true);

            // set the specific IDataTypeFactory if needed
            config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new XxxDataTypeFactory());
        }
    }

    /**
     * Create dbUnit {@link PrepAndExpectedTestCase} for running dbUnit database
     * tests.
     *
     * @param dataFileLoader
     *            The {@link DataFileLoader} used to load the test's specified
     *            data files.
     * @param databaseTester
     *            The {@link IDatabaseTester} used to run the tests against the
     *            database.
     * @return Configured dbUnit {@link PrepAndExpectedTestCase} for running
     *         dbUnit database tests.
     */
    @Bean
    public PrepAndExpectedTestCase prepAndExpectedTestCase(
            final DataFileLoader dataFileLoader,
            final IDatabaseTester databaseTester)
    {
        return new MyPrepAndExpectedTestCase(dataFileLoader, databaseTester);
    }

    /**
     * Create dbUnit {@link DataFileLoader} for loading the test's dbUnit data
     * files.
     *
     * @param ddr
     *            Your local class containing the replacement definitions.
     * @return Configured dbUnit {@link DataFileLoader} for loading the test's
     *         dbUnit data files.
     */
    @Bean
    public DataFileLoader dataFileLoader(final DbunitDataReplacement ddr)
    {
        final Map<String, Object> replacementObjects = ddr.getReplacementObjects();
        final Map<String, Object> replacementSubstrings = ddr.getReplacementSubstrings();
        return new FlatXmlDataFileLoader(replacementObjects, replacementSubstrings);
    }

    /**
     * Create dbUnit {@link IDatabaseTester}.
     *
     * @param dataSource
     *            The {@link DataSource} for the dbUnit test to use.
     * @return Configured dbUnit {@link IDatabaseTester}.
     */
    @Bean
    public IDatabaseTester databaseTester(final DataSource dataSource)
    {
        final DataSource dataSourceProxy = new TransactionAwareDataSourceProxy(dataSource);

        final IDatabaseTester databaseTester = new DataSourceDatabaseTester(dataSourceProxy);
        databaseTester.setTearDownOperation(DatabaseOperation.DELETE_ALL);

        return databaseTester;
    }
}
/**
 * Class containing replacement objects and replacement substrings for
 * substitution in dbUnit datasets.
 */
@Component
public class DbUnitDataReplacement
{
    private final Map<String, Object> replacementObjects = new HashMap<>();
    private final Map<String, Object> replacementSubstrings = new HashMap<>();

    public DbUnitDataReplacement()
    {
        populateReplacementObjects();
        populateReplacementSubstrings();
    }

    /**
     * Make replacement objects and populate the map with them.
     */
    private void populateReplacementObjects()
    {
        replacementObjects.put("[IGNORE]", null);
        replacementObjects.put("[NULL]", null);
        replacementObjects.put("[TIMESTAMP_TODAY]", TestDatabaseDates.TIMESTAMP_TODAY);
        replacementObjects.put("[TIMESTAMP_TOMORROW]", TestDatabaseDates.TIMESTAMP_TOMORROW);
        replacementObjects.put("[TIMESTAMP_YESTERDAY]", TestDatabaseDates.TIMESTAMP_YESTERDAY);
    }

    /**
     * Make replacement substrings and populate the map with them.
     */
    private void populateReplacementSubstrings()
    {
    }

    public Map<String, Object> getReplacementObjects()
    {
        return replacementObjects;
    }

    public Map<String, Object> getReplacementSubstrings()
    {
        return replacementSubstrings;
    }
}
/**
 * Dates for testing with database dates.
 */
@Component
public class TestDatabaseDates
{
    public static final Period ONE_DAY = Period.ofDays(1);

    public static final Instant NOW = Instant.now();

    public static final Timestamp TIMESTAMP_TODAY = asTimestamp(NOW);
    public static final Timestamp TIMESTAMP_TOMORROW = asTimestamp(NOW.plus(ONE_DAY));
    public static final Timestamp TIMESTAMP_YESTERDAY = asTimestamp(NOW.minus(ONE_DAY));

    public static Timestamp asTimestamp(final Instant instant)
    {
        return Timestamp.from(instant);
    }
}

Test Examples

Note: use good constant names for table names and table definitions, not generic "TABLEn" as used in these examples.

These examples show:

  1. Before the test runs, insert into the database the dataset contents of the files (represented by the constants COMMON_TABLE1, COMMON_TABLE2, TABLE3_PREP, TABLE4_PREP) as configured by the setup operation (defined outside of the test).
  2. After the test runs, verify table3 and table5 contents are the same as the expected data files and according to the VerifyTableDefinitions.
  3. After the test runs, cleanup tables as configured by the teardown operation (defined outside of the test).

Common Classes for Examples

It is helpful to make classes for common test table configurations as the configuration is usually the same for most tests with some tests slightly deviating. The following examples show some of these options.

TableNames

A simple class of table names, providing consistency and preventing typos.

Make this a production class when additionally specifying table names in classes such as entities and repositories/DAOs.

public abstract class TableNames
{
    public static final String TABLE3 = "table3";
    public static final String TABLE5 = "table5";
}

ColumnNames

A simple class of column names, providing consistency and preventing typos.

Make this a production class when additionally specifying column names in classes such as entities and repositories/DAOs.

public abstract class ColumnNames
{
    public static final String COLUMN1 = "column1";
}

ValueComparers

For specific ValueComparer configurations, it is helpful to isolate them in one or more classes, possibly organized by table.

public abstract class AppValueComparers
{
    public static final Map<String, ValueComparer> COLUMN1_GREATER =
        new ColumnValueComparerMapBuilder()
            .add(ColumnNames.COLUMN1, ValueComparers.isActualGreaterThanExpected)
            .build();
}

VerifyTableDefinitions

Static VerifyTableDefinition Instances

Typically, most tests' VerifyTableDefinitions are the same. Some tests' VerifyTableDefinitions needs may deviate on an ignored column or a specific column ValueComparer.

In this example:

  • "table3" in this class has the same configuration for any test, represented by the TABLE3 constant.
  • "table5" in this class has two configurations:
    1. TABLE5 has all columns using equality comparison
    2. TABLE5_COLUMN1_GREATER has all columns using equality comparison except COLUMN1 using ValueComparers.isActualGreaterThanExpected, verifying the COLUMN1 actual value results in a larger value than the expected value.
public abstract class VerifyTableDefinitions
{
    public static final VerifyTableDefinition TABLE3 = make(TableNames.TABLE3);
    public static final VerifyTableDefinition TABLE5 = make(TableNames.TABLE5);
    public static final VerifyTableDefinition TABLE5_COLUMN1_GREATER = make(TableNames.TABLE5, ValueComparers.COLUMN1_GREATER);

    private static VerifyTableDefinition make(final String tableName)
    {
        return new VerifyTableDefinition(tableName, null);
    }

    private static VerifyTableDefinition make(final String tableName, final Map<String, ValueComparer> columnValueComparers)
    {
        return new VerifyTableDefinition(tableName, null, columnValueComparers);
    }

    private static VerifyTableDefinition make(final String tableName, final ValueComparer defaultValueComparer, final Map<String, ValueComparer> columnValueComparers)
    {
        return new VerifyTableDefinition(tableName, defaultValueComparer, columnValueComparers);
    }
}
Test-Specific VerifyTableDefinitions

The above VerifyTableDefinitions example class used static VerifyTableDefinition instances. Some ValueComparers require test-specific values so static instances won’t work when reused across tests. In these cases, make a parameterized VerifyTableDefinition factory method to take the needed values.

For example, a test may need to specify a different set of "in values" than other tests, such as with the ConditionalSetBiValueComparer. It uses a ValueFactory to determine which of two ValueComparers to use for each table row, so make a factory method with the needed values parameters.

The following example’s factory method takes a list of IDs (called "in values") for a column’s values requiring a different ValueComparer than the rest of the rows (called "not in values"). Comparing columns happens as configured:

  • COLUMN1 uses the specified "isActualGreaterThanOrEqualToExpected"
  • COLUMN2 uses the specified "ConditionalSetBiValueComparer",
    • Table rows with an ID in the specified list will use the "inValuesValueComparer" for it, which is "isActualGreaterThanExpected", verifying the value changed and increased.
    • Table rows without an ID in the specified list will use the "notInValuesValueComparer" for it, which is "isActualEqualToExpected", verifying the value did not change.
  • The remaining columns will use the default, which is equality comparison, verifying the value did not change.
public abstract class VerifyTableDefinitionFactory
{
    public static VerifyTableDefinition tableName_update(Long... ids)
    {
        return make(TableName.TABLE_NAME, ValueComparerMapFactory.makeTableName_updated(ids));
    }
}
public abstract class ValueComparerMapFactory
{
    public static Map<String, ValueComparer> makeTableName_updated(Long[] ids)
    {
        Set<Long> values = new HashSet<>(Arrays.asList(ids));
        ValueComparer inValuesValueComparer = ValueComparers.isActualGreaterThanExpected;
        ValueComparer notInValuesValueComparer = ValueComparers.isActualEqualToExpected;
        ValueFactory<Long> valueFactory = (table, rowNum) -> {
            Number id = (Number) table.getValue(rowNum, ColumnName.COLUMN1);
            return id.longValue();
        };
        ValueComparer conditionalSetBiValueComparer = new ConditionalSetBiValueComparer<Long>(valueFactory, values, inValuesValueComparer, notInValuesValueComparer);

        return new ColumnValueComparerMapBuilder()
                .add(ColumnName.COLUMN1, ValueComparers.isActualGreaterThanOrEqualToExpected)
                .add(ColumnName.COLUMN2, conditionalSetBiValueComparer)
                .build();
    }
}

The test then uses the factory method (tableName_update) instead of a constant.

Using Default Equality Column Comparison

This test uses the default equality column comparison for all columns - the VerifyTableDefinitions used do not specify any ValueComparers so it defaults to equality.

public class DefaultEqualityComparisonExampleTest
{
    // this path is on classpath, e.g. in src/test/resources
    private static final String DBUNIT_DATA_DIR = "/dbunit/equality/";

    private static final String TABLE3_PREP = DBUNIT_DATA_DIR + "table3-prep.xml";
    private static final String TABLE4_PREP = DBUNIT_DATA_DIR + "table4-prep.xml";

    private static final String TABLE3_EXPECTED = DBUNIT_DATA_DIR + "table3-expected.xml";
    private static final String TABLE5_EXPECTED = DBUNIT_DATA_DIR + "table5-expected.xml";

    @Inject
    private PrepAndExpectedTestCase testCase;

    @Test
    public void testExample() throws Exception
    {
        // COMMON_TABLE1 and COMMON_TABLE2 are defined in common location, such as parent class
        // with value such as "src/test/resources/dbunit/common/table1.xml"

        final VerifyTableDefinition[] verifyTables = { VerifyTableDefinitions.TABLE3, VerifyTableDefinitions.TABLE5 };
        final String[] prepDataFiles = { COMMON_TABLE1, COMMON_TABLE2, TABLE3_PREP, TABLE4_PREP };
        final String[] expectedDataFiles = { TABLE3_EXPECTED, TABLE5_EXPECTED };

        testCase.runTest(verifyTables, prepDataFiles, expectedDataFiles, () -> {
            // execute test steps that exercise production code
            // e.g. call repository/DAO, call REST service

            // assert responses or other values

            // after this method exits, dbUnit will:
            //  * verify configured tables
            //  * cleanup tables as configured

            return null; // or an object for use/assert outside the Steps
        });
    }
}

Using ValueComparer Column Comparison

This test uses the default equality column comparison for all but one column - "table5"'s VerifyTableDefinition specifies a ValueComparer for "column1".

Note the only differences between this test and the prior test are:

  1. This test uses VerifyTableDefinitions.TABLE5_COLUMN1_GREATER instead of VerifyTableDefinitions.TABLE5
  2. The directory location of the prep and expected files
public class ValueComparerComparisonExampleTest
{
    // this path is on classpath, e.g. in src/test/resources
    private static final String DBUNIT_DATA_DIR = "/dbunit/valuecomparer/";

    private static final String TABLE3_PREP = DBUNIT_DATA_DIR + "table3-prep.xml";
    private static final String TABLE4_PREP = DBUNIT_DATA_DIR + "table4-prep.xml";

    private static final String TABLE3_EXPECTED = DBUNIT_DATA_DIR + "table3-expected.xml";
    private static final String TABLE5_EXPECTED = DBUNIT_DATA_DIR + "table5-expected.xml";

    @Inject
    private PrepAndExpectedTestCase testCase;

    @Test
    public void testExample() throws Exception
    {
        // COMMON_TABLE1 and COMMON_TABLE2 are defined in common location, such as parent class
        // with value such as "src/test/resources/dbunit/common/table1.xml"

        final VerifyTableDefinition[] verifyTables = { VerifyTableDefinitions.TABLE3, VerifyTableDefinitions.TABLE5_COLUMN1_GREATER };
        final String[] prepDataFiles = { COMMON_TABLE1, COMMON_TABLE2, TABLE3_PREP, TABLE4_PREP };
        final String[] expectedDataFiles = { TABLE3_EXPECTED, TABLE5_EXPECTED };

        testCase.runTest(verifyTables, prepDataFiles, expectedDataFiles, () -> {
            // execute test steps that exercise production code
            // e.g. call repository/DAO, call REST service

            // assert responses or other values

            // after this method exits, dbUnit will:
            //  * verify configured tables
            //  * cleanup tables as configured

            return null; // or an object for use/assert outside the Steps
        });
    }
}

Sharing Common (but not all) Prep or Expected Data Among Test Methods

As with the examples above, usually each test method requires its own prep and expected data so the test methods will each define their own.

Often, we can define dataset files of test data used across multiple tests, typically master lists and a base set of data useful to multiple tests. As above, place them in separate files (usually by table) for easy reuse.

Then, pass the needed ones in the correct data file array (as shown in the examples).

Java 8+ and Anonymous Interfaces

Release 2.5.2 introduced interface PrepAndExpectedTestCaseSteps and the PrepAndExpectedTestCase#runTest(VerifyTableDefinition[, String[], String[], PrepAndExpectedTestCaseSteps)] method. This allows for encapsulating test steps into an anonymous inner class or a Java 8+ lambda.

@Inject
private PrepAndExpectedTestCase testCase;

@Test
public void testExample() throws Exception
{
    final VerifyTableDefinition[] verifyTables = {}; // define tables to verify
    final String[] prepDataFiles = {}; // define prep files
    final String[] expectedDataFiles = {}; // define expected files
    final PrepAndExpectedTestCaseSteps testSteps = () -> {
        // execute test steps that exercise production code
        // e.g. call repository/DAO, call REST service

        // assert responses or other values

        // after this method exits, dbUnit will:
        //  * verify configured tables
        //  * cleanup tables as configured

        return null; // or an object for use/assert outside the Steps
    };

    testCase.runTest(verifyTables, prepDataFiles, expectedDataFiles, testSteps);
}

When using a version prior to Java 8, either use a class (concrete or anonymous inner class) for PrepAndExpectedTestCaseSteps or the following idiom that uses a try/catch/finally template:

@Inject
private PrepAndExpectedTestCase testCase;

@Test
public void testExample() throws Exception
{
    try
    {
        final VerifyTableDefinition[] verifyTables = {}; // define tables to verify
        final String[] prepDataFiles = {}; // define prep files
        final String[] expectedDataFiles = {}; // define expected files

        testCase.preTest(verifyTables, prepDataFiles, expectedDataFiles);

        // execute test steps that exercise production code
        // e.g. call repository/DAO, call REST service

        // assert responses or other values
    } catch (Exception e)
    {
        log.error("Test error.", e);
        throw e;
    } finally
    {
        // verify configured tables and cleanup tables as configured
        testCase.postTest();
    }
}

See Also

A test exercising code that spans two or more databases can prep and verify each database around one run of the code under test with MultiDataSourcePrepAndExpectedTestCase, which wraps one PrepAndExpectedTestCase delegate per data source.