Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ org.dbunit
├── assertion/ DbUnitAssert, ValueComparer implementations
├── ext/ Database-specific overrides (db2/, h2/, hsqldb/, mssql/, mysql/, oracle/, postgresql/)
├── ant/ Apache Ant task support
├── junit/jupiter/ DbUnitExtension: JUnit 5/6 (Jupiter) lifecycle integration
└── util/ SQLHelper, QualifiedTableName, RelativeDateTimeParser, etc.
```

Expand Down
10 changes: 10 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,11 @@
<artifactId>junit-platform-suite-engine</artifactId>
<version>${junitVersion}</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-testkit</artifactId>
<version>${junitVersion}</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
Expand Down Expand Up @@ -512,6 +517,11 @@
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite-engine</artifactId>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-testkit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
Expand Down
5 changes: 4 additions & 1 deletion src/changes/changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
</properties>

<body>
<release version="3.4.1-SNAPSHOT" date="TBD" description="A documentation-site overhaul (new tutorials, 9 database vendor guides, a class-by-class Core Components reference, consolidated Filters/Datasets/Operations pages, and a new Developing DbUnit contributor section covering coding standards, commit/changelog requirements, and the GitHub workflow); an opt-in all-column sort for tables without a primary key; an opt-in sort-on-filtered-columns-only mode for DefaultPrepAndExpectedTestCase fixing false failures on tables with a generated/identity first column; an optional DefaultPrepAndExpectedTestCase FailureHandler for collecting every mismatch instead of failing fast; MultiSchemaMySqlMetadataHandler for multi-schema MySQL connections; constructor-injected per-connection DatabaseConfig support for DataSourceDatabaseTester; new MariaDB support (MariaDbDataTypeFactory plus a Docker-backed IT profile); and multiple bug fixes including escape-pattern handling, primary-key filter fallback, empty-dataset DTD export, DatabaseDataSet initialization order, and a from-scratch clean-build pass across Javadoc doclint, Checkstyle, and compiler warnings">
<release version="3.4.1-SNAPSHOT" date="TBD" description="A documentation-site overhaul (new tutorials, 9 database vendor guides, a class-by-class Core Components reference, consolidated Filters/Datasets/Operations pages, and a new Developing DbUnit contributor section covering coding standards, commit/changelog requirements, and the GitHub workflow); a new JUnit 5/6 extension module (DbUnitExtension) for @ExtendWith-based lifecycle management; an opt-in all-column sort for tables without a primary key; an opt-in sort-on-filtered-columns-only mode for DefaultPrepAndExpectedTestCase fixing false failures on tables with a generated/identity first column; an optional DefaultPrepAndExpectedTestCase FailureHandler for collecting every mismatch instead of failing fast; MultiSchemaMySqlMetadataHandler for multi-schema MySQL connections; constructor-injected per-connection DatabaseConfig support for DataSourceDatabaseTester; new MariaDB support (MariaDbDataTypeFactory plus a Docker-backed IT profile); and multiple bug fixes including escape-pattern handling, primary-key filter fallback, empty-dataset DTD export, DatabaseDataSet initialization order, and a from-scratch clean-build pass across Javadoc doclint, Checkstyle, and compiler warnings">
<action dev="jeffjensen" type="add" issue="840" system="github" due-to="jeffjensen">
Add repo-root README.adoc, rendered natively by GitHub via Asciidoctor, so the repository landing page shows a pitch, build/reproducible-build badges, a pointer to the "dbUnit in 5 Minutes" tutorial, and links to the documentation site, Maven coordinates, GitHub Discussions, and CONTRIBUTING.md instead of nothing.
</action>
Expand Down Expand Up @@ -233,6 +233,9 @@
<action dev="jeffjensen" type="add" issue="865" system="github" due-to="jeffjensen">
Add DefaultPrepAndExpectedTestCase.setFailureHandler(FailureHandler), letting verifyData() use a caller-supplied FailureHandler such as DiffCollectingFailureHandler instead of the default fail-fast DefaultFailureHandler; unset (null) keeps the pre-existing default behavior unchanged.
</action>
<action dev="jeffjensen" type="add" issue="751" system="github" due-to="jeffjensen">
Add DbUnitExtension, a JUnit 5/6 extension that manages IDatabaseTester lifecycle via BeforeTestExecutionCallback and AfterTestExecutionCallback, enabling composition-based DbUnit tests without subclassing DatabaseTestCase.
</action>
</release>
<release version="3.4.0" date="Jul 28, 2026" description="Test-suite hardening (un-skip and strengthen dozens of disabled/no-op tests); add CachingConnectionProvider and reduce DefaultPrepAndExpectedTestCase's per-test connection churn; pin identifier case-folding to Locale.ENGLISH for Turkish-locale correctness; and a broad set of correctness fixes across export formats (XML, YAML, CSV, XLS, Ant), TimestampDataType timezone handling, InsertOperation/TransactionOperation, and resource-leak cleanups">
<action dev="jeffjensen" type="fix" issue="797" system="github" due-to="jeffjensen">
Expand Down
154 changes: 154 additions & 0 deletions src/main/java/org/dbunit/junit/jupiter/DbUnitExtension.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
*
* The DbUnit Database Testing Framework
* Copyright (C)2002-2025, 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.junit.jupiter;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

import org.dbunit.IDatabaseTester;
import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* JUnit 5/6 extension for DbUnit that manages the {@link IDatabaseTester} lifecycle
* around each test method.
*
* <p>Calls {@link IDatabaseTester#onSetup()} immediately before the test method
* (after all {@code @BeforeEach} callbacks) and {@link IDatabaseTester#onTearDown()}
* immediately after the test method (before any {@code @AfterEach} callbacks).
*
* <p>The extension discovers the {@link IDatabaseTester} by scanning the test
* instance's fields, including inherited fields, for a non-static field assignable
* to {@link IDatabaseTester}: the nearest declaring class (test class before
* superclass) wins, but that class must declare exactly one such field—two or
* more at the same level is rejected as ambiguous. Configure the tester—including
* its dataset—in a {@code @BeforeEach} method; those run before this extension's
* setup callback:
*
* <pre>{@code
* @ExtendWith(DbUnitExtension.class)
* class MyDatabaseTest {
* IDatabaseTester databaseTester = new JdbcDatabaseTester("driver", "url", "user", "pass");
*
* @BeforeEach
* void loadDataset() throws Exception {
* databaseTester.setDataSet(new FlatXmlDataSetBuilder().build(...));
* }
*
* @Test
* void testSomething() { ... }
* }
* }</pre>
*
* <p><strong>Note:</strong> {@code @Nested} test classes are not supported. Field
* discovery only scans the innermost test instance and its superclasses—not
* enclosing class instances—since a Java nested class does not extend its
* enclosing class.
*
* @author dbunit
* @since 3.4.1
*/
public class DbUnitExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback {
private static final Logger log = LoggerFactory.getLogger(DbUnitExtension.class);

private static final ExtensionContext.Namespace NAMESPACE =
ExtensionContext.Namespace.create(DbUnitExtension.class);

private static final String TESTER_KEY = "databaseTester";

/**
* Runs database setup before the test method executes.
*
* @param context The extension context for the test method.
* @throws Exception If resolving the {@link IDatabaseTester} field or its onSetup() call fails.
*/
@Override
public void beforeTestExecution(final ExtensionContext context) throws Exception {
final IDatabaseTester tester = resolveTester(context);
context.getStore(NAMESPACE).put(TESTER_KEY, tester);
tester.onSetup();
}

/**
* Runs database teardown after the test method executes.
*
* @param context The extension context for the test method.
* @throws Exception If the stored {@link IDatabaseTester}'s onTearDown() call fails.
*/
@Override
public void afterTestExecution(final ExtensionContext context) throws Exception {
final IDatabaseTester tester =
context.getStore(NAMESPACE).get(TESTER_KEY, IDatabaseTester.class);
if (tester != null) {
tester.onTearDown();
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

private IDatabaseTester resolveTester(final ExtensionContext context) throws Exception {
final Object testInstance = context.getTestInstance()
.orElseThrow(() -> new IllegalStateException(
"No test instance available in ExtensionContext."));

Class<?> clazz = testInstance.getClass();
while (clazz != null && clazz != Object.class) {
final Field field = findTesterField(clazz, testInstance);
if (field != null) {
field.setAccessible(true);
final IDatabaseTester tester = (IDatabaseTester) field.get(testInstance);
if (tester == null) {
throw new IllegalStateException("IDatabaseTester field '"
+ field.getName() + "' in "
+ testInstance.getClass().getName() + " is null.");
}
log.debug("Resolved IDatabaseTester '{}' in {}",
field.getName(), testInstance.getClass().getName());
return tester;
}
clazz = clazz.getSuperclass();
}

throw new IllegalStateException("No IDatabaseTester field found in "
+ testInstance.getClass().getName()
+ " or its superclasses. Declare a non-static field whose type implements IDatabaseTester"
+ " to use DbUnitExtension.");
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
}

private Field findTesterField(final Class<?> clazz, final Object testInstance) {
Field match = null;
for (final Field field : clazz.getDeclaredFields()) {
if (!Modifier.isStatic(field.getModifiers())
&& IDatabaseTester.class.isAssignableFrom(field.getType())) {
if (match != null) {
throw new IllegalStateException("Multiple IDatabaseTester fields found in "
+ clazz.getName() + ": '" + match.getName() + "' and '"
+ field.getName() + "'. Declare exactly one non-static field"
+ " whose type implements IDatabaseTester in "
+ testInstance.getClass().getName() + ".");
}
match = field;
}
}
return match;
}
}
4 changes: 4 additions & 0 deletions src/site/asciidoc/testcases.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ that predate this recommendation; see
link:testcases/MigratingToIDatabaseTester.html[Migrating to IDatabaseTester]
if you want to convert an existing `DBTestCase` subclass.

On JUnit 5/6 (Jupiter), composition users can also skip writing the
`@BeforeEach`/`@AfterEach` lifecycle calls entirely — see
link:testcases/DbUnitExtension.html[DbUnitExtension].

Whichever style you choose,
link:testcases/PrepAndExpectedTestCase.html[PrepAndExpectedTestCase] is
usually a better starting point than driving either one directly — it
Expand Down
67 changes: 67 additions & 0 deletions src/site/asciidoc/testcases/DbUnitExtension.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
= DbUnitExtension
Jeff Jensen

== Overview

link:/dbunit/apidocs/org/dbunit/junit/jupiter/DbUnitExtension.html[DbUnitExtension]
is a JUnit 5/6 (Jupiter) extension that drives the
link:IDatabaseTester.html[IDatabaseTester] setup/teardown lifecycle
automatically, so a test class doesn't need its own `@BeforeEach`/`@AfterEach`
pair calling `onSetup()`/`onTearDown()`.

Register it with `@ExtendWith(DbUnitExtension.class)`. The test class still
holds its own `IDatabaseTester` field — this is the same composition style as
link:IDatabaseTester.html[the IDatabaseTester guide], just with the lifecycle
calls automated instead of written by hand:

[source,java]
----
@ExtendWith(DbUnitExtension.class)
class AccountRepositoryTest
{
IDatabaseTester databaseTester =
new JdbcDatabaseTester("org.h2.Driver", "jdbc:h2:mem:example;DB_CLOSE_DELAY=-1");

@BeforeEach
void loadDataset() throws Exception
{
databaseTester.setDataSet(new FlatXmlDataSetBuilder().build(new File("prep.xml")));
databaseTester.setTearDownOperation(DatabaseOperation.DELETE_ALL);
}

@Test
void testWithdraw_sufficientBalance_decrementsBalance() { ... }
}
----

`@BeforeEach` methods still run first — configure the dataset and any
operation overrides there. The extension then calls `onSetup()` immediately
before the test method and `onTearDown()` immediately after it, even if the
test method fails or `onSetup()` itself throws.

== Field Discovery

The extension finds the `IDatabaseTester` by scanning the test instance's
fields, including inherited ones:

* The nearest declaring class wins — a field on the test class itself takes
precedence over one on a superclass.
* That class must declare exactly one non-static field assignable to
`IDatabaseTester`. No match anywhere in the hierarchy, or two-or-more
matches at the same class level, both fail fast with a descriptive
`IllegalStateException` rather than guessing.
* Static fields are ignored.
* Private fields are found; the field's own access modifier doesn't matter.

NOTE: `@Nested` test classes are not supported — field discovery only walks
the innermost test instance and its superclasses, not enclosing class
instances, since a Java nested class does not extend its enclosing class.

== When to Use This Instead of Manual Lifecycle Calls

Reach for `DbUnitExtension` when a `@BeforeEach`/`@AfterEach` pair that only
calls `onSetup()`/`onTearDown()` (as shown in link:IDatabaseTester.html[the
IDatabaseTester guide]) would just be boilerplate repeated across every test
class. Write the `@BeforeEach`/`@AfterEach` pair yourself instead when a test
needs other logic around those calls, or targets a JUnit version this
extension doesn't support.
5 changes: 5 additions & 0 deletions src/site/asciidoc/testcases/IDatabaseTester.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ this lifecycle (plus expected-data verification) behind `runTest()`/
`preTest()`/`postTest()`, and is usually a better starting point than driving
`onSetup()`/`onTearDown()` directly — see its page for the full API.

TIP: On JUnit 5/6 (Jupiter), link:DbUnitExtension.html[`DbUnitExtension`] can
call `onSetup()`/`onTearDown()` for you, removing those two calls from the
`@BeforeEach`/`@AfterEach` pair shown above. Keep `@BeforeEach` if it still
configures the tester, e.g. via `setDataSet()`/`setTearDownOperation()`.

Comment thread
coderabbitai[bot] marked this conversation as resolved.
[#IOperationListenerHooks]
== IOperationListener Hooks

Expand Down
1 change: 1 addition & 0 deletions src/site/site.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
<item name="Getting Started" href="/howto.html"/>
<item name="Test Integration" href="/testcases.html">
<item name="IDatabaseTester Guide" href="/testcases/IDatabaseTester.html"/>
<item name="DbUnitExtension" href="/testcases/DbUnitExtension.html"/>
<item name="PrepAndExpectedTestCase" href="/testcases/PrepAndExpectedTestCase.html"/>
<item name="JdbcBasedDBTestCase" href="/testcases/JdbcBasedDBTestCase.html"/>
<item name="DataSourceBasedDBTestCase" href="/testcases/DataSourceBasedDBTestCase.html"/>
Expand Down
Loading
Loading