Class DbUnitExtension

java.lang.Object
org.dbunit.junit.jupiter.DbUnitExtension
All Implemented Interfaces:
org.junit.jupiter.api.extension.AfterTestExecutionCallback, org.junit.jupiter.api.extension.BeforeTestExecutionCallback, org.junit.jupiter.api.extension.Extension, org.junit.jupiter.api.extension.ParameterResolver, org.junit.jupiter.api.extension.TestInstantiationAwareExtension

public class DbUnitExtension extends Object implements org.junit.jupiter.api.extension.BeforeTestExecutionCallback, org.junit.jupiter.api.extension.AfterTestExecutionCallback, org.junit.jupiter.api.extension.ParameterResolver
JUnit 5/6 extension for DbUnit that manages the dbUnit lifecycle around each test method, driven by the org.dbunit.annotation family: DbUnitPrep, DbUnitSetup, DbUnitExpected, DbUnitTearDown, and DbUnitConfig. Add @DbUnitTest (or @ExtendWith(DbUnitExtension.class) directly) to opt a test class in.

The two paths

Without DbUnitExpected, this is the setup/teardown path: the DbUnitPrep dataset (if any) and DbUnitSetup operation are applied, then IDatabaseTester.onSetup() runs before the test method and IDatabaseTester.onTearDown() after it. The test method asserts results however it likes.

With DbUnitExpected present, the test switches onto the PrepAndExpectedTestCase prep/expected path: configureTest() and preTest() run before the test method, postTest() after it - postTest() compares the database against the DbUnitExpected dataset, so the verifying is done for you, skipping it when the test method itself already failed so a verification failure never masks the real cause. Neither path is a reduced form of the other; they run different lifecycles.


 @DbUnitTest
 class AccountRepositoryTest {
     IDatabaseTester databaseTester;

     AccountRepositoryTest() throws ClassNotFoundException {
         databaseTester = new JdbcDatabaseTester("driver", "url", "user", "pass");
     }

     @Test
     @DbUnitPrep("accounts-prep.xml")
     @DbUnitExpected(value = "accounts-expected.xml", verifyTables = {"ACCOUNT"})
     void testWithdraw_sufficientBalance_decrementsBalance() { ... }
 }
 

Resolving the tester or test case

First match wins:

  1. A field annotated DbUnitTestCase, whose type implements PrepAndExpectedTestCase - that instance is driven directly.
  2. A field annotated DbUnitTester, whose type implements IDatabaseTester.
  3. DbUnitConfig.databaseTesterFactory(), reflectively instantiated and asked to create a tester.
  4. The original (3.5.0) 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.

Programmatic setup

Without annotations, configure the tester - including its dataset - in a @BeforeEach method; those run before this extension's setup callback:


 @ExtendWith(DbUnitExtension.class)
 class MyDatabaseTest {
     IDatabaseTester databaseTester;

     MyDatabaseTest() throws ClassNotFoundException {
         databaseTester = new JdbcDatabaseTester("driver", "url", "user", "pass");
     }

     @BeforeEach
     void loadDataset() throws Exception {
         databaseTester.setDataSet(new FlatXmlDataSetBuilder().build(...));
     }

     @Test
     void testSomething() { ... }
 }
 

This zero-annotation style is the 3.5.0 lifecycle: the extension calls only onSetup()/onTearDown() on the tester and runs the row count check around them, without replacing the tester's IOperationListener. The connection it resolves for the row count baseline is never closed before onSetup() - a tester that returns one fixed connection from every call (e.g. a DefaultDatabaseTester built from a fixed connection) would otherwise run onSetup() against a closed one - and is closed after the test when closeConnectionAfterTest allows and the tester's listener is not NO_OP. Adding any @DbUnit* annotation or a @DbUnitTester/@DbUnitTestCase field opts the test into the fuller annotation-driven lifecycle described above.

Parameter injection

Also a ParameterResolver for IDatabaseTester, PrepAndExpectedTestCase, IDatabaseConnection, and Connection parameters on @Test and @BeforeEach methods - but only for a test that opts into the org.dbunit.annotation family: one carrying @DbUnitTest or any @DbUnit* annotation (DbUnitConfig, DbUnitPrep, DbUnitSetup, DbUnitExpected, DbUnitTearDown, DbUnitRowCountCheck) on the class or method, or a @DbUnitTester/@DbUnitTestCase field. A bare @ExtendWith(DbUnitExtension.class) class with only a plain, unannotated IDatabaseTester field keeps the 3.5.0 lifecycle behaviour untouched and has no parameter claimed here, so another extension resolving a Connection (or any of the other three types) on the same test stays unambiguous. Switch such a class to @DbUnitTest, or mark its field @DbUnitTester, to opt its parameters in.

On an annotation-driven test this extension claims every Connection parameter on a @Test/@BeforeEach method. Another extension on the same test that also resolves java.sql.Connection - Spring's SpringExtension, Testcontainers, a JPA harness - then collides with JUnit's "discovered multiple competing ParameterResolvers". Set @DbUnitConfig(injectConnectionParameter = false) to yield the bare Connection to that extension; the dbUnit-specific IDatabaseConnection parameter is still resolved (no other framework claims that type) - inject it and call getConnection().

The injected IDatabaseConnection/Connection is managed for the test's duration and closed afterward - by this extension on the setup/teardown path, by the driven PrepAndExpectedTestCase on the prep/expected path; do not close it yourself. A @BeforeEach parameter resolves before beforeTestExecution(ExtensionContext) runs - JUnit Jupiter always calls @BeforeEach methods first - so it sees the tester/connection exactly as they exist before this extension's own setup: DbUnitPrep's dataset, if any, is not loaded onto it yet.

Not safe for concurrent execution of test methods that resolve to the same tester or test case instance - e.g. a static @DbUnitTester/ @DbUnitTestCase field, or any instance field at all under @TestInstance(Lifecycle.PER_CLASS) - since this extension applies annotations and drives the dbUnit lifecycle against it without synchronization. Run such test methods sequentially (JUnit Jupiter's own default) rather than under junit.jupiter.execution.parallel.enabled=true, or give each test method's own tester its own non-shared field.

Since:
3.5.0
Author:
Jeff Jensen
See Also:
  • Nested Class Summary

    Nested classes/interfaces inherited from interface org.junit.jupiter.api.extension.TestInstantiationAwareExtension

    org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope
  • Constructor Summary

    Constructors
    Constructor
    Description
    Creates the extension.
  • Method Summary

    Modifier and Type
    Method
    Description
    void
    afterTestExecution(org.junit.jupiter.api.extension.ExtensionContext context)
    Runs the stored AnnotatedTestExecutor's after-test steps.
    void
    beforeTestExecution(org.junit.jupiter.api.extension.ExtensionContext context)
    Resolves the configured AnnotatedTestExecutor - reusing one already resolved for this test by parameter injection into a @BeforeEach method, if any - and runs its before-test steps.
    resolveParameter(org.junit.jupiter.api.extension.ParameterContext parameterContext, org.junit.jupiter.api.extension.ExtensionContext extensionContext)
    Resolves a parameter from the same AnnotatedTestExecutor that beforeTestExecution(ExtensionContext) drives - reusing it (creating and caching it on first use, for a @BeforeEach parameter resolved before beforeTestExecution runs) rather than resolving a second tester or test case independently.
    boolean
    supportsParameter(org.junit.jupiter.api.extension.ParameterContext parameterContext, org.junit.jupiter.api.extension.ExtensionContext extensionContext)
    Reports whether this extension resolves the given parameter: an IDatabaseTester, PrepAndExpectedTestCase, IDatabaseConnection, or Connection parameter, declared on the current test method or a @BeforeEach method - the only two contexts afterTestExecution(ExtensionContext) still runs after, so a connection this executor resolves is guaranteed to still be open, and eventually closed, for the parameter's entire lifetime.

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

    Methods inherited from interface org.junit.jupiter.api.extension.TestInstantiationAwareExtension

    getTestInstantiationExtensionContextScope
  • Constructor Details

    • DbUnitExtension

      public DbUnitExtension()
      Creates the extension.
  • Method Details

    • beforeTestExecution

      public void beforeTestExecution(org.junit.jupiter.api.extension.ExtensionContext context) throws Exception
      Resolves the configured AnnotatedTestExecutor - reusing one already resolved for this test by parameter injection into a @BeforeEach method, if any - and runs its before-test steps.
      Specified by:
      beforeTestExecution in interface org.junit.jupiter.api.extension.BeforeTestExecutionCallback
      Parameters:
      context - The extension context for the test method.
      Throws:
      Exception - If resolving the tester or test case, or running the before-test steps, fails.
    • afterTestExecution

      public void afterTestExecution(org.junit.jupiter.api.extension.ExtensionContext context) throws Exception
      Runs the stored AnnotatedTestExecutor's after-test steps.

      A failure here is simply left to propagate rather than caught and reconciled by hand: the JUnit Platform engine invokes every AfterTestExecutionCallback through the same ThrowableCollector that already collected the test method's own outcome (if any), so the engine itself already applies the correct precedence - a genuine failure here is promoted over a mere test abort from the test method (e.g. a failed Assumptions.assumeTrue()), with the abort attached as a suppressed exception on the promoted failure instead of the failure being silently swallowed into the discarded abort; against a real test method failure, this failure is the one attached as suppressed, so JUnit still reports the original failure as the cause. Reimplementing that precedence here would only risk getting it wrong.

      Specified by:
      afterTestExecution in interface org.junit.jupiter.api.extension.AfterTestExecutionCallback
      Parameters:
      context - The extension context for the test method.
      Throws:
      Exception - If the after-test steps fail.
    • supportsParameter

      public boolean supportsParameter(org.junit.jupiter.api.extension.ParameterContext parameterContext, org.junit.jupiter.api.extension.ExtensionContext extensionContext) throws org.junit.jupiter.api.extension.ParameterResolutionException
      Reports whether this extension resolves the given parameter: an IDatabaseTester, PrepAndExpectedTestCase, IDatabaseConnection, or Connection parameter, declared on the current test method or a @BeforeEach method - the only two contexts afterTestExecution(ExtensionContext) still runs after, so a connection this executor resolves is guaranteed to still be open, and eventually closed, for the parameter's entire lifetime. A matching parameter on any other method (constructor, @BeforeAll, @AfterEach, @AfterAll) is declined here rather than handed out already-closed - or, worse, opened for the first time with nothing left to ever close it - since @AfterEach and @AfterAll run after afterTestExecution(ExtensionContext) already has.

      Also declined - regardless of type or method - unless the test opts into the org.dbunit.annotation family (see isAnnotationDriven(ExtensionContext)). A bare @ExtendWith(DbUnitExtension.class) class with only a plain, unannotated IDatabaseTester field is the 3.5.0 lifecycle-only style: it never claimed a parameter before this method existed, and does not now, so another extension resolving one of these types - Connection especially - on the same test is not left competing with an unconditional claim here. An annotation-driven test can still opt its bare Connection parameter back out with @DbUnitConfig(injectConnectionParameter = false) while keeping the other three types.

      Specified by:
      supportsParameter in interface org.junit.jupiter.api.extension.ParameterResolver
      Parameters:
      parameterContext - The parameter to resolve.
      extensionContext - The extension context for the test method.
      Returns:
      true for a matching parameter type on the test method or a @BeforeEach method of an annotation-driven test.
      Throws:
      org.junit.jupiter.api.extension.ParameterResolutionException - Never thrown directly; declared by the ParameterResolver contract.
    • resolveParameter

      public Object resolveParameter(org.junit.jupiter.api.extension.ParameterContext parameterContext, org.junit.jupiter.api.extension.ExtensionContext extensionContext) throws org.junit.jupiter.api.extension.ParameterResolutionException
      Resolves a parameter from the same AnnotatedTestExecutor that beforeTestExecution(ExtensionContext) drives - reusing it (creating and caching it on first use, for a @BeforeEach parameter resolved before beforeTestExecution runs) rather than resolving a second tester or test case independently.

      A PrepAndExpectedTestCase parameter requires a @DbUnitTestCase field: without one, the executor may not have constructed its own instance yet - see AnnotatedTestExecutor.getPrepAndExpectedTestCase() - so there is no single instance to inject.

      Specified by:
      resolveParameter in interface org.junit.jupiter.api.extension.ParameterResolver
      Parameters:
      parameterContext - The parameter to resolve.
      extensionContext - The extension context for the test method.
      Returns:
      The resolved IDatabaseTester, PrepAndExpectedTestCase, IDatabaseConnection, or Connection value.
      Throws:
      org.junit.jupiter.api.extension.ParameterResolutionException - If no test instance exists yet, or if resolution otherwise fails. The first is not reachable through JUnit Jupiter's own engine for a parameter this class actually claims - a static-scope context such as @BeforeAll is already declined by supportsParameter(ParameterContext, ExtensionContext), so the engine never calls this method for it at all - only a caller invoking this method directly, without checking that first, could still hit it.