DbUnit Annotations
Overview
org.dbunit.annotation declares the dataset and operation for a test
directly on the class or method, instead of writing @BeforeEach/
@AfterEach calls or driving PrepAndExpectedTestCase
by hand. On JUnit 5/6 (Jupiter), add @DbUnitTest to opt a class in — it is
exactly @ExtendWith(DbUnitExtension.class):
@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
}
}These annotations target the fixed, per-method case — one test method, one known dataset, one known expectation. See When Not to Use These Annotations below for the cases they deliberately do not cover, and what to use instead.
At a Glance
One pass through the whole vocabulary before the detail below — each row names the section with its full rules:
| Annotation | Use it for |
|---|---|
@DbUnitTest |
Opts a class into DbUnitExtension — exactly @ExtendWith(DbUnitExtension.class). |
@DbUnitPrep |
Dataset file(s) to load before the test — see @DbUnitPrep and @DbUnitSetup. |
@DbUnitSetup |
The operation to load them with; needed only to change the CLEAN_INSERT default — see @DbUnitPrep and @DbUnitSetup. |
@DbUnitExpected |
Dataset file(s) to verify after the test; presence switches the test onto the prep/expected path — see @DbUnitExpected, @DbUnitVerifyTable, @DbUnitColumnComparer. |
@DbUnitVerifyTable, @DbUnitColumnComparer |
Full per-table/per-column verify rules, nested inside @DbUnitExpected — see @DbUnitExpected, @DbUnitVerifyTable, @DbUnitColumnComparer. |
@DbUnitTearDown |
The cleanup operation to run after the test; defaults to doing nothing — see @DbUnitPrep and @DbUnitSetup. |
@DbUnitConfig |
Class-level wiring: loader, tester factory, DatabaseConfig properties, catalog default, base directory, failure handler, connection-close behavior — see @DbUnitConfig. |
@DbUnitProperty |
One DatabaseConfig name/value pair, nested inside @DbUnitConfig — see DatabaseConfig Properties. |
@DbUnitTester, @DbUnitTestCase |
Field markers injecting an already-built IDatabaseTester / PrepAndExpectedTestCase, in place of the plain-field auto-scan — see Resolving the Tester or Test Case. |
@DbUnitRowCountCheck |
Fails the test when a table’s row count moved between setup and teardown, catching a table teardown forgot or wrongly cleaned — see @DbUnitRowCountCheck. |
A catalog class, or a DataSetPathsProvider / DatabaseConfigPropertiesProvider / VerifyTableDefinitionsProvider / DatabaseTesterFactory implementation |
Sharing dataset paths, properties, verify-table definitions, or tester construction across many test classes — see The Shared VerifyTableDefinition Catalog and Reusing a Whole Configuration: Composed Annotations. |
The Two Paths
Presence of @DbUnitExpected decides which path a test runs:
| Path | Behaviour |
|---|---|
| Setup/teardown | @DbUnitPrep’s dataset (if any) and `@DbUnitSetup’s
operation are applied, then `IDatabaseTester.onSetup() runs before the test
method and onTearDown() after it — the same lifecycle
DbUnitExtension has always run. The test method
asserts results however it likes. |
| Prep/expected | @DbUnitExpected present. @DbUnitPrep’s dataset (if any)
and `@DbUnitSetup’s operation are applied the same as the setup/teardown
path, then the extension drives a
PrepAndExpectedTestCase:
`configureTest()/preTest() before the test method,
postTest() after it. postTest() compares the database against the
@DbUnitExpected dataset, so the verifying is done for you. Verification is
skipped when the test method itself already failed, so a verification
failure never masks the real cause. |
Choose by what you want verified: the setup/teardown path when the test method does its own asserting (or just needs seeded data), the prep/expected path when you want dbUnit’s expected-dataset comparison to do it. Neither is a reduced form of the other — they run different lifecycles.
The setup/teardown path’s reach is deliberately wide: @DbUnitPrep/
@DbUnitSetup/@DbUnitTearDown drive onSetup()/onTearDown() on any
IDatabaseTester implementation - built-in or a project’s own. A project’s
own implementation should override getOperationListener() (returning the
value setOperationListener() was last given, the way AbstractDatabaseTester
does) so the extension wraps that listener rather than replacing it with a
fresh closing one - relevant when the tester relies on a non-closing listener
to protect a shared connection. The prep/expected path is narrower, but by
necessity rather than omission:
expected-data verification is the entire reason @DbUnitExpected exists, and
it is a capability IDatabaseTester does not have at all -
PrepAndExpectedTestCase is the only dbUnit interface that defines it, so it
is the only thing this path can target. A class that already extends some
other dbUnit test-case base class (e.g. JdbcBasedDBTestCase) and wants both
that base class’s behavior and @DbUnitExpected support needs to also
implement PrepAndExpectedTestCase itself, the way
DefaultPrepAndExpectedTestCase already does - @DbUnitTestCase resolves
against that interface, not against any particular base class.
Both paths run the same tester/test-case resolution below, and both honor
@DbUnitConfig.
Resolving the Tester or Test Case
First match wins:
| Order | Source |
|---|---|
| 1 | A field annotated @DbUnitTestCase, whose type implements
PrepAndExpectedTestCase — that instance is driven directly; the extension
constructs nothing. The "the object already exists, inject it" case. |
| 2 | A field annotated @DbUnitTester, whose type implements
IDatabaseTester. |
| 3 | @DbUnitConfig(databaseTesterFactory = …) — the named
DatabaseTesterFactory is instantiated and asked for a tester
(Provider and Factory Classes). Needed because IDatabaseTester implementations have no
uniform no-arg constructor (JdbcDatabaseTester needs a driver and URL,
DataSourceDatabaseTester a DataSource, JndiDatabaseTester a lookup
name). |
| 4 | The original (3.5.0) field auto-scan: exactly one non-static field
assignable to IDatabaseTester, nearest declaring class wins. Unchanged, so
every 3.5.0 test keeps working untouched. |
Field discovery walks every test instance in scope, innermost first, so a
@Nested test class inherits its enclosing class’s tester field - or
shadows it outright with a field of its own, the same marker or not, the
same way a @Nested class’s own unmarked IDatabaseTester field already
shadows an enclosing one. Ambiguity is only ever rejected with
IllegalStateException within one test instance’s own class hierarchy:
two fields with the same marker there, or both a @DbUnitTester and a
@DbUnitTestCase field declared on that same instance.
A @DbUnitTestCase field resolving tier 1 does not, by itself, remove the
need for a tester: tier 3-4 still run afterward - tier 2’s @DbUnitTester
field is not consulted here, see below - unless the injected instance’s type
overrides getDatabaseTester()/setDatabaseTester() and already carries its
own - DefaultPrepAndExpectedTestCase does - since the extension’s own
machinery - installing the @DbUnitProperty listener, and any
IDatabaseTester parameter injection - needs one regardless of test case
type. For such an implementation with none set yet, the tester found this way
is also wired onto it. Any other PrepAndExpectedTestCase implementation may
override those same two methods to get the same automatic wiring; one that
does not needs databaseTesterFactory configured even when it manages its
own connection internally - otherwise resolution fails with "No
IDatabaseTester field found" for a test case that otherwise needs nothing
external. A @DbUnitTester field is not a substitute here: it is rejected
outright alongside @DbUnitTestCase (see above), and even if it were not,
this fallback does not consult one. In that case, the factory-resolved
tester must be the exact instance the injected test case actually uses
internally, or @DbUnitSetup/@DbUnitTearDown operations set on it may
silently never reach what it actually runs.
@DbUnitTester and @DbUnitTestCase both accept a static field, the way a
tester or test case shared across every method in a class is typically
declared:
@DbUnitTest
class AccountRepositoryTest
{
@DbUnitTester
static final IDatabaseTester databaseTester;
static
{
try
{
databaseTester =
new JdbcDatabaseTester("org.h2.Driver", "jdbc:h2:mem:example;DB_CLOSE_DELAY=-1");
}
catch (ClassNotFoundException e)
{
throw new ExceptionInInitializerError(e);
}
}
@Test
@DbUnitPrep("/dbunit/accounts/prep.xml")
void testFindAll() { ... }
}A static field is one instance shared by every test method in the class — safe
under JUnit 5/6’s default sequential execution, but not under parallel execution
(junit.jupiter.execution.parallel.enabled=true with per-class or per-method concurrency):
concurrent test methods would drive the same IDatabaseTester/PrepAndExpectedTestCase
instance’s onSetup()/onTearDown()/dataset state at the same time, racing each other. Keep
a class sharing a static @DbUnitTester/@DbUnitTestCase field running sequentially, or
give each test method its own non-static field instead.
@DbUnitPrep and @DbUnitSetup
DbUnitOperation has eight values, each mapping to the corresponding
DatabaseOperation constant:
| Value | Meaning |
|---|---|
CLEAN_INSERT |
Deletes all rows then inserts the dataset rows. The
default @DbUnitSetup operation. |
INSERT |
Inserts dataset rows. Fails if a row already exists. |
UPDATE |
Updates existing rows in the dataset. |
REFRESH |
Inserts or updates rows (upsert). |
DELETE |
Deletes rows matching the dataset. |
DELETE_ALL |
Deletes all rows from each table in the dataset. |
TRUNCATE_TABLE |
Truncates each table in the dataset. |
NONE |
Performs no operation. The default @DbUnitTearDown operation. |
Data and operation are deliberately separate annotations rather than one fused annotation:
| Before the test | After the test | |
|---|---|---|
| Data | @DbUnitPrep(files) |
@DbUnitExpected(files, …) |
| Operation | @DbUnitSetup(operation) |
@DbUnitTearDown(operation) |
The split matters because a method-level annotation overrides a class-level one wholesale — attributes do not merge. Fused, a class-level operation would be silently lost the moment a method declared its own files. Split, the two resolve independently:
@DbUnitTest
@DbUnitSetup(operation = DbUnitOperation.REFRESH) // class-wide, set once
@DbUnitTearDown(operation = DbUnitOperation.DELETE_ALL)
class AccountRepositoryTest
{
IDatabaseTester databaseTester;
AccountRepositoryTest() throws ClassNotFoundException
{
databaseTester =
new JdbcDatabaseTester("org.h2.Driver", "jdbc:h2:mem:example;DB_CLOSE_DELAY=-1");
}
@Test
@DbUnitPrep("a.xml") // still uses REFRESH
void testOne() { ... }
@Test
@DbUnitPrep("b.xml") // still uses REFRESH
void testTwo() { ... }
}Because the default is already right, the common case is a single
annotation — @DbUnitPrep("accounts.xml") alone means
DbUnitOperation.CLEAN_INSERT. @DbUnitSetup with no @DbUnitPrep at all
is valid: no dataset is prepared, but the operation is still set on the
tester and applies to whatever dataset the tester already holds — most
usefully NONE, to suppress a dataset the tester was already carrying. A
non-NONE operation there needs the tester to already hold a dataset (set
in a @BeforeEach); a bare @DbUnitSetup resolves to CLEAN_INSERT, not
NONE, so @DbUnitSetup with no @DbUnitPrep and no @BeforeEach dataset
logs a WARN (a built-in tester then fails inside onSetup(); a custom one
may source its dataset elsewhere). Both annotations otherwise apply the same
way on either path: @DbUnitSetup’s operation runs before `preTest() too
when @DbUnitExpected is present, not just before onSetup() on the
setup/teardown path.
On the prep/expected path, both the setup and teardown operations are
always set on the tester, even with none of @DbUnitPrep, @DbUnitSetup,
or @DbUnitTearDown declared — setup resolves to
DbUnitOperation.CLEAN_INSERT, teardown to DbUnitOperation.NONE, and both
are applied regardless, since preTest() and cleanupData() always run on
that path. On the setup/teardown path, when @DbUnitPrep, @DbUnitSetup, or
@DbUnitTearDown is not declared, the test runs with the tester’s own
dataset / setup operation / teardown operation — preserving a @BeforeEach
method’s own setDataSet()/setSetUpOperation()/setTearDownOperation().
Nothing bleeds between test methods. Each method’s resolved annotations —
its own, plus any inherited from its class or a parent class — fully
determine what gets applied for that method, on either path. Before the
method the tester’s dataset and setup/teardown operations are snapshotted,
and after it they are restored, so a static @DbUnitTester field (or any
tester under @TestInstance(PER_CLASS)) never carries one method’s
@DbUnitPrep/@DbUnitSetup/@DbUnitTearDown — or the operations the
prep/expected path applies unconditionally — onto the next. A @BeforeEach
value, being re-set every method, survives.
@DbUnitTearDown is @DbUnitSetup’s counterpart, defaulting to
`DbUnitOperation.NONE. It has no data annotation of its own: the dataset
already prepared (prep, or prep plus expected on the other path) is what the
operation runs against. To tear down an extra table, list it — empty — in
the prep dataset instead.
@DbUnitExpected, @DbUnitVerifyTable, @DbUnitColumnComparer
@DbUnitExpected names the expected dataset file(s) and switches the test
onto the prep/expected path. Valid with no @DbUnitPrep at all — the
database state already present is what gets verified.
Which tables to verify, and with what rules, escalates through four forms so the common cases stay short:
| Written | Meaning |
|---|---|
| nothing | Verify every table in the expected dataset, default rules per table. |
verifyTables = {"ACCOUNT", "TXN"} |
Verify exactly these tables. Narrows a
class- or method-level verifyDefinitions catalog to just these tables when
one is in play (see the catalog section below); with no catalog
at all, a default VerifyTableDefinition per named table instead — avoids
repeating a bare @DbUnitVerifyTable per table just to name them. |
verify = @DbUnitVerifyTable(…) |
Full per-table rules: column include/exclude, comparers, sort mode. |
verifyDefinitions = AppVerifyTables.class |
Definitions from a shared
catalog class, optionally narrowed by verifyTables — see
the catalog section below. The one that scales to a whole suite. |
verify together with verifyDefinitions or verifyTables is rejected as
ambiguous. Use verifyTables only to narrow a catalog selected by
verifyDefinitions.
That table’s four forms plus the rejected combination are the five branches resolution actually walks, strictly in this order - highest priority first:
verifyset together withverifyDefinitionsorverifyTables- rejected.verifyDefinitions- this method’s own, else (when this method sets neither it norverify) the class-level@DbUnitConfig.verifyDefinitionsdefault - narrowed toverifyTableswhen that is also set.verifyalone.verifyTablesalone, with no catalog in play from either level.- None of the above.
Setting verify together with either of the other two fails fast rather
than silently picking one:
@DbUnitExpected(value = "expected.xml",
verifyTables = "ACCOUNT", // IllegalStateException: ambiguous with verify() below
verify = @DbUnitVerifyTable("ACCOUNT"))
void testAmbiguous() { ... } // never runs - rejected before the test executesA worked example combining a prep file, an expected file, and per-table rules:
@Test
@DbUnitPrep("/dbunit/accounts/prep.xml")
@DbUnitExpected(value = "/dbunit/accounts/expected.xml",
verify = @DbUnitVerifyTable(value = "ACCOUNT",
exclude = "CREATED_TS",
columnComparers = @DbUnitColumnComparer(column = "BALANCE",
comparer = IsActualGreaterThanExpectedValueComparer.class)))
@DbUnitTearDown(operation = DbUnitOperation.DELETE_ALL)
void testWithdraw_sufficientBalance_decrementsBalance() { ... }@DbUnitColumnComparer.comparer() and @DbUnitVerifyTable.defaultComparer()
are reflectively instantiated with their no-arg constructor. That reaches
any custom ValueComparer, and the stateless ValueComparers constants —
those whose implementation class has a no-arg constructor. It does not
reach a ValueComparers constant that is a configured instance built with
constructor arguments — the timestamp-tolerance comparers, for example
isActualWithinOneMinuteNewerOfExpectedTimestamp; naming one of those here
fails fast with an IllegalStateException pointing at
the catalog as the fix, since a configured comparer can only be
expressed as a VerifyTableDefinition constant written in Java.
include() has one quirk worth knowing: VerifyTableDefinition
distinguishes null (include every column) from an empty array (include
nothing), but an annotation cannot express null. An empty include()
therefore maps to null — include all — the only sensible default, since
"include nothing" is a degenerate setting nobody wants.
The Shared VerifyTableDefinition Catalog
A provider covering the case where every test wants the same fixed table
set is not how real suites actually verify data — they keep one shared
class holding a VerifyTableDefinition constant per table, and each test
picks a different subset. verifyDefinitions names one or more catalog
classes, and verifyTables selects from them by table name:
public class AppVerifyTables
{
public static final VerifyTableDefinition ACCOUNT =
new VerifyTableDefinition("ACCOUNT", new String[] {"ID", "CREATED_TS"});
public static final VerifyTableDefinition TRANSACTION =
new VerifyTableDefinition("TRANSACTION", new String[] {"ID"},
ValueComparers.isActualEqualToExpected,
new ColumnValueComparerMapBuilder()
.add("POSTED_TS", ValueComparers.isActualWithinOneMinuteNewerOfExpectedTimestamp)
.build());
public static final VerifyTableDefinition CUSTOMER =
new VerifyTableDefinition("CUSTOMER", new String[] {"UPDATED_TS"});
}@DbUnitTest
@DbUnitConfig(verifyDefinitions = AppVerifyTables.class)
@DbUnitTearDown(operation = DbUnitOperation.DELETE_ALL)
class AccountRepositoryTest
{
IDatabaseTester databaseTester;
AccountRepositoryTest() throws ClassNotFoundException
{
databaseTester =
new JdbcDatabaseTester("org.h2.Driver", "jdbc:h2:mem:example;DB_CLOSE_DELAY=-1");
}
@Test
@DbUnitPrep("accounts-prep.xml")
@DbUnitExpected(value = "accounts-expected.xml",
verifyTables = {"ACCOUNT", "TRANSACTION"})
void testWithdraw_sufficientBalance_decrementsBalance() { }
@Test
@DbUnitPrep("accounts-prep.xml")
@DbUnitExpected(value = "customers-expected.xml",
verifyTables = {"CUSTOMER"})
void testRename_existingCustomer_updatesName() { }
@Test
@DbUnitPrep("accounts-prep.xml")
@DbUnitExpected("full-expected.xml")
void testFullRefresh_noVerifyAttributeAtAll_verifiesEveryCatalogDefinition() { }
}The third method sets neither verify nor verifyDefinitions itself, so
resolution falls through to the class-level @DbUnitConfig.verifyDefinitions
default with no verifyTables to narrow it - every definition in
AppVerifyTables (ACCOUNT, TRANSACTION, and CUSTOMER) is verified.
The catalog is a plain constants class — no interface, no boilerplate,
nothing to keep in sync. This is deliberately the same shape
PrepAndExpectedTestCase
already teaches with its TableNames/ColumnNames/AppValueComparers/
VerifyTableDefinitions helper classes, so an existing project’s constants
work here unchanged.
verifyDefinitions sits on both @DbUnitConfig (the class-level default)
and @DbUnitExpected (a per-method override); the two resolve independently,
the same rule as everywhere else in this family. A catalog class may
instead implement VerifyTableDefinitionsProvider when its definitions must
be computed rather than declared as constants — one attribute covers both:
implementing classes are instantiated and asked, plain classes have their
public static final VerifyTableDefinition fields read directly. The same
table name defined in two catalog classes named together is rejected rather
than silently resolved by declaration order.
Provider and Factory Classes
Four attributes take a class instead of an inline value, for configuration shared across many test classes that a Java annotation cannot express as a constant:
| Attribute | Interface | Method asked |
|---|---|---|
@DbUnitPrep(provider = …), @DbUnitExpected(provider = …) |
DataSetPathsProvider |
getDataSetPaths() |
@DbUnitConfig(propertiesProvider = …) |
DatabaseConfigPropertiesProvider |
getDatabaseConfigProperties() |
@DbUnitConfig(verifyDefinitions = …), @DbUnitExpected(verifyDefinitions = …) |
VerifyTableDefinitionsProvider |
getVerifyTableDefinitions() |
@DbUnitConfig(databaseTesterFactory = …) |
DatabaseTesterFactory |
getDatabaseTester() |
All four share one contract: the named class is instantiated once through its
own public no-arg constructor and asked for its one value. A missing or
throwing constructor fails fast with an IllegalStateException naming the
attribute. A provider returning null is rejected; so is one returning
nothing, where returning nothing is a misconfiguration rather than a valid
"none" (an empty getDataSetPaths(), a getDatabaseTester() returning
null). Setting an inline value and a provider for the same attribute is
rejected — set one.
verifyDefinitions is the odd one: its class is only optionally a
VerifyTableDefinitionsProvider; a plain constants class works too and is
the common case (The Shared VerifyTableDefinition Catalog).
@DbUnitConfig
Class-level wiring, carried automatically two separate ways: @Inherited, so
a subclass of a project base class gets it too; and, independently, via
composed-annotation lookup (Reusing a Whole Configuration: Composed Annotations), so a project’s own annotation
carrying @DbUnitConfig as a meta-annotation is found the same as a
directly-declared one - a different mechanism from @Inherited, not a
consequence of it:
| Attribute | Purpose |
|---|---|
dataFileLoader |
The DataFileLoader used for every dataset file.
Defaults to
FileExtensionDataFileLoader,
which dispatches by extension — see Data File Loader. |
databaseTesterFactory |
See resolution order tier 3. |
prepAndExpectedTestCase |
The PrepAndExpectedTestCase implementation to
construct for the prep/expected path when no @DbUnitTestCase field
supplies one, via a (DataFileLoader, IDatabaseTester, boolean)
constructor — the same shape DefaultPrepAndExpectedTestCase itself has.
Defaults to DefaultPrepAndExpectedTestCase. In practice this means a
DefaultPrepAndExpectedTestCase subclass (to override a hook such as
setUpDatabaseConfig()): a from-scratch PrepAndExpectedTestCase would
have to reimplement dataset loading, comparison, and cleanup — see
@DbUnitConfig and an injected @DbUnitTestCase. |
properties / propertiesProvider |
DatabaseConfig properties to apply —
see DatabaseConfig Properties below. Mutually exclusive. |
verifyDefinitions |
The class-level catalog default — see The Shared VerifyTableDefinition Catalog. |
dataSetBaseDir |
A classpath directory prefix — see Dataset Path Resolution below. |
failureHandler |
A FailureHandler for verification failures, in place of
dbUnit’s own default. |
closeConnectionAfterTest |
Whether the connection this executor resolves -
for the prep/expected path, the row count check, or parameter injection - is
closed after each test; default true. Set false when the tester shares a
CachingConnectionProvider across test methods. Also left open, regardless of
this attribute, when the tester’s IOperationListener is
NO_OP_OPERATION_LISTENER - the established signal that its connection is
managed elsewhere. |
injectConnectionParameter |
Whether the extension resolves a bare
java.sql.Connection test-method parameter; default true. Set false when
a co-registered resolver (Spring, Testcontainers) needs it — see
DbUnitExtension. |
@DbUnitConfig and an injected @DbUnitTestCase
dataFileLoader, failureHandler, properties/propertiesProvider,
closeConnectionAfterTest, and @DbUnitRowCountCheck are also pushed into a
@DbUnitTestCase-injected PrepAndExpectedTestCase after it is constructed,
so a shared instance built without them still runs with the configured values.
This targets DefaultPrepAndExpectedTestCase and its subclasses, which
override every setter involved.
Any other PrepAndExpectedTestCase implementation is best-effort: each
configured attribute whose setter the instance’s type does not override
fails fast at test time with an IllegalStateException naming the
attribute, rather than silently accepting a value it cannot apply.
closeConnectionAfterTest is the exception — a non-overriding instance is
warned, not failed, because this extension’s own connection (for the row
count check or parameter injection) honors the value regardless.
Do not inject a bare or unstubbed mock as @DbUnitTestCase when using these
attributes. That check asks only which class declares each setter, so a mock —
or any type that mechanically redeclares every interface method — reports as
overriding all of them: a configured attribute is then handed to a body that
does nothing with it, with no failure and no warning. Inject a real
implementation.
DatabaseConfig Properties
@DbUnitProperty entries are collected into a Properties instance and
handed to DatabaseConfig.setPropertiesByString(Properties), which accepts
both the long http://www.dbunit.org/…; names and their short forms:
@DbUnitConfig(properties = {
@DbUnitProperty(name = "caseSensitiveTableNames", value = "true"),
@DbUnitProperty(name = "batchSize", value = "50")
})Named members are used instead of a String[] of "name=value" pairs so
each entry is self-documenting, IDE-completable, and cannot be broken by a
value that itself contains = (an escape pattern, for instance). For
properties shared across several test classes, propertiesProvider names a
DatabaseConfigPropertiesProvider implementation instead — instantiated and
asked for the Properties to apply (Provider and Factory Classes). Setting both
properties and propertiesProvider is rejected.
How properties reach the connection differs by path. On the
setup/teardown path, the extension applies @DbUnitProperty values to
whichever connection it ends up using - the tester’s own onSetup()/
onTearDown() retrieve
independently, via the tester’s IOperationListener, and this executor’s
own memoized connection, applied the moment it resolves one - so in practice
they reach the connection on every test where IDatabaseTester#getConnection()
returns non-null, regardless of the setup/teardown operations. That memoized
connection itself is resolved as cheaply as possible: when the tester is a
real AbstractDatabaseTester with a non-NONE setup operation, this
executor never resolves a connection of its own at all - it reuses the one
onSetup() was already about to retrieve, the same connection
@DbUnitProperty reaches either way; only a custom IDatabaseTester
implementation, or a NONE setup operation with nothing else to retrieve
one, makes this executor resolve a connection eagerly by itself. On
the prep/expected path, the test case applies them directly to the connection
it resolves for configureTest()/setupData()/verifyData()/cleanupData()
— that path never goes through the tester’s IOperationListener at all, so
the setup/teardown operation does not matter (see @DbUnitConfig and an injected @DbUnitTestCase
for what an injected non-DefaultPrepAndExpectedTestCase instance needs).
On the prep/expected path, DefaultPrepAndExpectedTestCase applies
@DbUnitProperty values through its setUpDatabaseConfig(DatabaseConfig)
hook. A subclass that overrides setUpDatabaseConfig() — the pre-3.6.0 way to
configure a DatabaseConfig — must call super.setUpDatabaseConfig(config),
or the @DbUnitProperty values are silently dropped. The extension logs a
warning when it detects such an override alongside properties/propertiesProvider,
since it cannot tell from reflection whether the super call is there. Either
add the super call, or drop @DbUnitProperty and configure the
DatabaseConfig entirely in the override.
@DbUnitRowCountCheck
@DbUnitRowCountCheck turns on the
row count check for a class or method: it snapshots
every table’s row count before @DbUnitPrep loads, snapshots again after teardown, and
fails the test by name when any count moved — catching a table the test forgot to list for
teardown (whose rows survive and break a later test) and equally a reference table it
wrongly listed (whose count went down).
Because the baseline is taken before @DbUnitPrep, a passing check means teardown
returned the database to its pre-prep state — the prep data removed too, not just the
test’s own writes. @DbUnitRowCountCheck with @DbUnitPrep and no @DbUnitTearDown (the
NONE default) therefore fails: the prep rows are still there. Pair it with
@DbUnitTearDown(operation = DbUnitOperation.DELETE_ALL), as the example below does.
@DbUnitTest
@DbUnitRowCountCheck
class AccountRepositoryTest
{
IDatabaseTester databaseTester = new JdbcDatabaseTester(...);
@Test
@DbUnitPrep("accounts-prep.xml")
@DbUnitTearDown(operation = DbUnitOperation.DELETE_ALL)
void testWithdraw_sufficientBalance_decrementsBalance() { ... }
}enabled() defaults to true, so the bare annotation means "on"; it exists for the
override direction — a class-level check with one method that legitimately leaves rows
behind (a test asserting that an audit trail persists, say) writes
@DbUnitRowCountCheck(enabled = false) on that method rather than losing the check for the
whole class.
Four sources can speak, highest priority first:
| Order | Source |
|---|---|
| 1 | The -Ddbunit.rowCountCheck system property, when present — wins outright, in both
directions, so it can force-enable across a CI run and force-disable locally without
editing code. |
| 2 | @DbUnitRowCountCheck — method level, else class level. |
| 3 | DatabaseConfig.FEATURE_ROW_COUNT_CHECK. |
| 4 | Default false. |
Three behaviours worth knowing explicitly, because each is a place a reader would otherwise guess wrong:
- A method-level
@DbUnitRowCountCheckoverrides a class-level one wholesale —exclude()included, the same rule as everywhere else in this family. A method that wants one extra exclusion has to repeat the class-level ones; they are not merged. - The check compares row counts, not row contents — an
UPDATEthat changes a value without changing how many rows are present passes. It catches a leaked or wrongly removed row, not a data mismatch; that is what@DbUnitExpectedverification is for. - On the prep/expected path (
@DbUnitExpecteddeclared), this annotation drives the check through the test case — see @DbUnitConfig and an injected @DbUnitTestCase for what an injected instance that is not aDefaultPrepAndExpectedTestCasesubclass needs. On the setup/teardown path (no@DbUnitExpected), the check runs directly against the connection regardless of test case type.
Reusing a Whole Configuration: Composed Annotations
For configuration shared across many test classes, compose one project
annotation carrying @DbUnitTest alongside @DbUnitConfig and the
lifecycle annotations. This works for free — annotation lookup finds
composed (meta-)annotations, not just directly-declared ones:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@DbUnitTest
@DbUnitConfig(dataFileLoader = FlatXmlDataFileLoader.class,
properties = @DbUnitProperty(name = "caseSensitiveTableNames", value = "true"))
@DbUnitSetup(operation = DbUnitOperation.REFRESH)
public @interface AppDatabaseTest {}Every test class then carries a single @AppDatabaseTest.
Dataset Path Resolution
Every @DbUnitPrep/@DbUnitExpected path resolves to an absolute classpath
resource in this order:
| Order | Rule |
|---|---|
| 1 | A path already starting with / — used as-is. Always wins, so a shared
file outside the base directory stays reachable. |
| 2 | @DbUnitConfig(dataSetBaseDir = "…"), when set — prefixed to the
path. |
| 3 | Otherwise — resolved relative to the test class’s package, matching the convention PrepAndExpectedTestCase already teaches. |
dataSetBaseDir exists because the dominant real-world idiom is a shared
directory constant concatenated onto every file name:
@DbUnitTest
@DbUnitConfig(dataSetBaseDir = "/dbunit/accounts/")
class AccountRepositoryTest
{
IDatabaseTester databaseTester;
AccountRepositoryTest() throws ClassNotFoundException
{
databaseTester =
new JdbcDatabaseTester("org.h2.Driver", "jdbc:h2:mem:example;DB_CLOSE_DELAY=-1");
}
@Test
@DbUnitPrep("accounts-prep.xml")
@DbUnitExpected("accounts-expected.xml")
@DbUnitTearDown(operation = DbUnitOperation.DELETE_ALL)
void testWithdraw_sufficientBalance_decrementsBalance() { }
}The dataset format is inferred from the file extension by the configured
dataFileLoader — see Data File Loader
for the extension table and for using a directory-based format (CSV) or
full (non-flat) XML instead.
When Not to Use These Annotations
We cannot write every dbUnit test with annotations.
You want dbUnit to seed data but verify results your own way. That is not
a gap — it is the setup/teardown path. Use @DbUnitPrep/
@DbUnitSetup/@DbUnitTearDown with no @DbUnitExpected, and assert in the
test method with whatever tooling you already have. @DbUnitExpected exists
only for the case where you want dbUnit’s expected-dataset comparison to do
the verifying; there is no annotation to substitute a different verifier for
it, because that comparison is `PrepAndExpectedTestCase’s whole job.
Annotation values require compile-time constants — a static final
String[], VerifyTableDefinition[], or Properties cannot be referenced
directly (JLS 9.7.1). A catalog class (The Shared VerifyTableDefinition Catalog), a provider class
(DataSetPathsProvider, DatabaseConfigPropertiesProvider,
VerifyTableDefinitionsProvider), or a composed annotation (Reusing a Whole Configuration: Composed Annotations)
is the way to share such values; each is described above.
The harder limit is structural, not a missing escape hatch: an annotation
is fixed at compile time on one method, and Java offers no mechanism for
one to vary per invocation. A @ParameterizedTest whose VerifyTableDefinition[]/
prep files/expected files change per @MethodSource row — the dominant
pattern in larger functional test suite scenarios — cannot be expressed with these
annotations at all. The programmatic runTest() API on
PrepAndExpectedTestCase remains the
right and fully supported tool for that case, and for suites that layer
non-dbUnit assertions (exit codes, exception types, notification content)
around the dbUnit portion of a test. Its
PrepAndExpectedTestData
overloads keep the per-row prep/expected/verify triple to a single
@MethodSource column and a single method parameter.
@DbUnitTestCase field injection still helps such suites, even without the
lifecycle annotations: it makes the harness wiring explicit on the field
instead of inherited from a superclass, and a shared catalog (The Shared VerifyTableDefinition Catalog)
gives the VerifyTableDefinition constants a first-class home that
non-parameterized tests in the same suite can point at by name.
DbUnitOperation is also narrower than DatabaseOperation: it covers the eight simple
operations only, with no declarative way to compose them (CompositeOperation) or wrap one
in a transaction (TransactionOperation). A test needing either drops to the programmatic
path the same way — configure the tester’s setup/teardown operation directly in a
@BeforeEach method, or override it on an injected PrepAndExpectedTestCase.


