DbUnitTestExecutionListener.java
/*
*
* The DbUnit Database Testing Framework
* Copyright (C)2002-2026, DbUnit.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package org.dbunit.spring;
import java.util.List;
import org.dbunit.IDatabaseTester;
import org.dbunit.PrepAndExpectedTestCase;
import org.dbunit.annotation.runtime.AnnotatedTestConfiguration;
import org.dbunit.annotation.runtime.AnnotatedTestExecutor;
import org.dbunit.annotation.runtime.AnnotatedTestOptIn;
import org.dbunit.annotation.runtime.AnnotationLookup;
import org.dbunit.annotation.runtime.TestInstanceTesterResolver;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
/**
* Spring TestContext Framework {@link org.springframework.test.context.TestExecutionListener} for
* DbUnit: drives the same {@code org.dbunit.annotation} family - {@code @DbUnitPrep},
* {@code @DbUnitSetup}, {@code @DbUnitExpected}, {@code @DbUnitTearDown}, and
* {@code @DbUnitConfig} - that {@link org.dbunit.junit.jupiter.DbUnitExtension} drives for
* JUnit 5/6, for a test run through Spring's TestContext Framework instead, with JUnit 5's
* {@code SpringExtension}. See {@code annotations.adoc} for the annotation vocabulary itself,
* which is identical either way.
*
* <p>Register it with {@link DbUnitSpringTest @DbUnitSpringTest}, or directly with
* {@code @TestExecutionListeners(listeners = DbUnitTestExecutionListener.class, mergeMode =
* MERGE_WITH_DEFAULTS)}. The {@code mergeMode} is not optional, and {@code DbUnitSpringTest}
* explains why, and what else stops it from registering the listener.
*
* <h2>Which tests it acts on</h2>
*
* <p>Only a test that opts into the {@code org.dbunit.annotation} family, by the rule
* {@code DbUnitExtension} applies for its annotation-driven lifecycle: any of
* {@code @DbUnitConfig}, {@code @DbUnitPrep}, {@code @DbUnitSetup}, {@code @DbUnitExpected},
* {@code @DbUnitTearDown} or {@code @DbUnitRowCountCheck} on the test method or class, or a
* {@code @DbUnitTester}/{@code @DbUnitTestCase} field. For any other test it does nothing, so
* {@link DbUnitSpringTest @DbUnitSpringTest} on a shared base class or composed annotation
* neither needs a tester for, nor fails, the tests that use no DbUnit. A plain, unmarked
* {@code IDatabaseTester} field is not an opt-in, and unlike {@code DbUnitExtension} there is no
* lifecycle-only style to fall back to for it.
*
* <h2>Resolving the tester or test case</h2>
*
* <p>First match wins:
* <ol>
* <li>A field annotated {@code @DbUnitTestCase}, whose type implements
* {@link org.dbunit.PrepAndExpectedTestCase} - that instance is driven directly.</li>
* <li>A field annotated {@code @DbUnitTester}, whose type implements
* {@link org.dbunit.IDatabaseTester}.</li>
* <li>{@code @DbUnitConfig(databaseTesterFactory = ...)}, reflectively instantiated and asked
* to create a tester.</li>
* <li>The 3.5.0-style auto-scan: exactly one non-static field assignable to
* {@code IDatabaseTester}.</li>
* <li>Spring-specific, and last: the {@code IDatabaseTester} bean in the test's
* {@code ApplicationContext} - the sole one, or when several exist the one Spring's candidate
* rules single out: the one marked {@code @Primary}, the only one not marked
* {@code @Fallback}, or the only default candidate - so a test class needs no dbUnit-specific
* field at all when its test configuration already exposes one, e.g. a
* {@code @TestConfiguration} bean wrapping the application's own {@code DataSource} in a
* {@link org.dbunit.DataSourceDatabaseTester}. No bean, or several that those rules do not tell
* apart, is not an error here, since an unused bean of this type is an unremarkable case for a
* Spring context.</li>
* </ol>
*
* <p>The first four tiers are {@code DbUnitExtension}'s, by the same rules: fields are found in
* the whole class hierarchy of the test instance, and a second marked field there is ambiguous.
* The bean should be a singleton: for a prototype-scoped bean the lookup creates a new instance
* that the test's own {@code @Autowired} field does not share.
*
* <h2>Shared testers</h2>
*
* <p>An {@code IDatabaseTester} bean is a singleton of the application context, and Spring caches
* a context and shares it among every test class with the same configuration, so one tester
* serves all of them. The listener sets the dataset and the operations on it for each test and
* restores them afterwards, and wraps its operation listener while the test runs, all without
* synchronization. A tester shared this way - or through a {@code static}
* {@code @DbUnitTester} field, or any instance field under
* {@code @TestInstance(Lifecycle.PER_CLASS)} - is therefore not safe for tests that run at the
* same time, such as under {@code junit.jupiter.execution.parallel.enabled=true}: run them
* sequentially, or give each its own tester.
*
* <h2>Nested test classes</h2>
*
* <p>For a JUnit Jupiter {@code @Nested} test class Spring inherits the enclosing class's
* configuration, and so does this listener, as {@code DbUnitExtension} does: the enclosing
* class's class-level annotations apply unless the nested class or its method declares its own,
* and the tester field is searched in the nested instance first, then in each enclosing instance
* outward. An enclosing instance is reachable only when the nested class uses it - see the
* {@code DbUnitTestExecutionListener} site page for how to work with that limit.
*
* <h2>Not supported: parameter injection</h2>
*
* <p>Unlike {@code DbUnitExtension}, this listener never injects an {@code IDatabaseTester},
* {@code PrepAndExpectedTestCase}, or connection as a test method parameter - Spring's
* {@code TestExecutionListener} has no equivalent of JUnit 5's {@code ParameterResolver}. Declare
* a {@code @DbUnitTester}/{@code @DbUnitTestCase} field, or autowire the {@code IDatabaseTester}
* bean, to reach it from the test method; {@code SpringExtension} itself resolves
* {@code @Autowired} test method parameters, so the bean can be a parameter too.
* {@code @DbUnitConfig(injectConnectionParameter)} is specific to the JUnit 5 binding and has no
* effect here.
*
* @author Jeff Jensen
* @since 3.6.0
* @see DbUnitSpringTest
*/
public class DbUnitTestExecutionListener extends AbstractTestExecutionListener
{
/**
* Runs between Spring's {@code DirtiesContextTestExecutionListener} (order 3000) and its
* {@code TransactionalTestExecutionListener} (order 4000), in the same before-test-method and
* after-test-method phases the latter starts and ends a {@code @Transactional} test's
* transaction in. Spring runs the before-callbacks in ascending and the after-callbacks in
* reverse order, so this listener's prep commits before that transaction opens, and its
* verify and teardown run after the transaction has ended but before {@code @DirtiesContext}
* closes the application context.
*
* <p>Its database work is therefore never part of that transaction: it neither joins it nor
* is rolled back by it. See the {@code DbUnitTestExecutionListener} site page for what that
* means for a test.
*/
public static final int ORDER = 3500;
/** Package-visible so tests can reference it instead of duplicating the literal. */
static final String EXECUTOR_ATTRIBUTE =
DbUnitTestExecutionListener.class.getName() + ".executor";
private final SpringTesterResolver testerResolver = new SpringTesterResolver();
/** Creates the listener; Spring instantiates it through this constructor. */
public DbUnitTestExecutionListener()
{
}
/**
* Resolves a fresh {@link AnnotatedTestExecutor} for this test method, keeps it on the
* {@link TestContext} for {@link #afterTestMethod(TestContext)}, and runs its before-test
* steps: the prep dataset and setup operation.
*
* <p>Runs in Spring's before-test-method phase, so ahead of JUnit's {@code @BeforeEach}
* methods and of the test-managed transaction of a {@code @Transactional} test, and after
* dependency injection - an {@code @Autowired} tester field is populated by now, but one
* assigned in a {@code @BeforeEach} method is not.
*
* <p>Spring's {@link TestContext}, attributes included, serves every test method of a class,
* so the executor is built anew for each method and removed again after it, never reused. It
* is stored before its before-test steps run, so a setup that fails part way is still
* released by {@link #afterTestMethod(TestContext)}.
*
* <p>Does nothing for a test that does not opt into the {@code org.dbunit.annotation}
* family - one with none of its configuring annotations on the method or class, and no
* {@code @DbUnitTester}/{@code @DbUnitTestCase} field - so
* {@link DbUnitSpringTest @DbUnitSpringTest} on a shared base class or composed annotation
* costs the tests that use no DbUnit nothing, not even a tester.
*
* @param testContext The test context.
* @throws Exception If resolving the tester or test case, or running the before-test steps,
* fails.
*/
@Override
public void beforeTestMethod(final TestContext testContext) throws Exception
{
final AnnotationLookup lookup = new SpringAnnotationLookup(testContext);
if (!isOptedIn(testContext, lookup))
{
return;
}
final Class<?> testClass = testContext.getTestClass();
final AnnotatedTestConfiguration configuration =
AnnotatedTestConfiguration.from(testClass, lookup);
final TestInstanceTesterResolver.Resolution resolution =
testerResolver.resolve(testContext, configuration);
final IDatabaseTester tester = resolution.getTester();
final PrepAndExpectedTestCase testCase = resolution.getTestCase();
final AnnotatedTestExecutor executor =
new AnnotatedTestExecutor(configuration, tester, testCase);
testContext.setAttribute(EXECUTOR_ATTRIBUTE, executor);
executor.beforeTest();
}
/**
* Removes the {@link AnnotatedTestExecutor} {@link #beforeTestMethod(TestContext)} kept for
* this test method and runs its after-test steps: the verification of
* {@code @DbUnitExpected} and the teardown operation. Does nothing when
* {@link #beforeTestMethod(TestContext)} never ran for this method, so it never tears down
* what another method set up. The executor is removed before its after-test steps run, so it
* is gone even when they fail.
*
* <p>Runs in Spring's after-test-method phase, so after JUnit's {@code @AfterEach} methods
* and after a {@code @Transactional} test's transaction has rolled back or committed: a
* verification sees only what that transaction committed.
*
* <p>Verification is skipped when the test failed - an exception from the test method, from a
* {@code @BeforeEach} or {@code @AfterEach} method, or from an extension - so a difference
* never masks the real failure.
*
* @param testContext The test context.
* @throws Exception If the after-test steps fail.
*/
@Override
public void afterTestMethod(final TestContext testContext) throws Exception
{
final AnnotatedTestExecutor executor =
(AnnotatedTestExecutor) testContext.removeAttribute(EXECUTOR_ATTRIBUTE);
if (executor == null)
{
return;
}
final Throwable testException = testContext.getTestException();
final boolean testFailed = testException != null;
executor.afterTest(testFailed);
}
/**
* Returns {@link #ORDER}.
*
* @return {@link #ORDER}.
*/
@Override
public int getOrder()
{
return ORDER;
}
private boolean isOptedIn(final TestContext testContext, final AnnotationLookup lookup)
{
final List<Class<?>> testClasses =
TestInstanceChain.classesInScope(testContext.getTestClass());
return AnnotatedTestOptIn.isOptedIn(lookup, testClasses);
}
}