View Javadoc
1   /*
2    *
3    * The DbUnit Database Testing Framework
4    * Copyright (C)2002-2026, DbUnit.org
5    *
6    * This library is free software; you can redistribute it and/or
7    * modify it under the terms of the GNU Lesser General Public
8    * License as published by the Free Software Foundation; either
9    * version 2.1 of the License, or (at your option) any later version.
10   *
11   * This library is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   * Lesser General Public License for more details.
15   *
16   * You should have received a copy of the GNU Lesser General Public
17   * License along with this library; if not, write to the Free Software
18   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19   *
20   */
21  package org.dbunit.junit.jupiter;
22  
23  import static org.assertj.core.api.Assertions.assertThat;
24  import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
25  
26  import java.sql.Connection;
27  import java.sql.ResultSet;
28  import java.sql.Statement;
29  
30  import org.dbunit.DatabaseEnvironment;
31  import org.dbunit.DatabaseProfile;
32  import org.dbunit.DatabaseTesterFactory;
33  import org.dbunit.DefaultDatabaseTester;
34  import org.dbunit.DefaultPrepAndExpectedTestCase;
35  import org.dbunit.IDatabaseTester;
36  import org.dbunit.IOperationListener;
37  import org.dbunit.JdbcDatabaseTester;
38  import org.dbunit.PrepAndExpectedTestCase;
39  import org.dbunit.PrepAndExpectedTestCaseSteps;
40  import org.dbunit.VerifyTableDefinition;
41  import org.dbunit.annotation.DbUnitConfig;
42  import org.dbunit.annotation.DbUnitExpected;
43  import org.dbunit.annotation.DbUnitPrep;
44  import org.dbunit.annotation.DbUnitTestCase;
45  import org.dbunit.annotation.DbUnitVerifyTable;
46  import org.dbunit.database.IDatabaseConnection;
47  import org.dbunit.dataset.IDataSet;
48  import org.dbunit.operation.DatabaseOperation;
49  import org.junit.jupiter.api.Test;
50  import org.junit.jupiter.api.extension.ExtendWith;
51  import org.junit.platform.testkit.engine.EngineTestKit;
52  import org.slf4j.LoggerFactory;
53  
54  import ch.qos.logback.classic.Level;
55  import ch.qos.logback.classic.Logger;
56  import ch.qos.logback.classic.spi.ILoggingEvent;
57  import ch.qos.logback.core.read.ListAppender;
58  
59  /**
60   * Real-database integration test of the composition-style {@link PrepAndExpectedTestCase}:
61   * a hand-written implementation (not a {@link DefaultPrepAndExpectedTestCase} subclass) that
62   * does <em>not</em> override {@code getDatabaseTester()}/{@code setDatabaseTester()} and manages
63   * its own tester and connection internally, injected through a {@code @DbUnitTestCase} field.
64   * {@code annotations.adoc} documents this pattern as supported (with
65   * {@code databaseTesterFactory} configured so the extension's own machinery still has a tester);
66   * only mock-based unit tests exercise the resolution fallback, so a real database confirms the
67   * full {@code configureTest} / {@code preTest} / {@code @DbUnitExpected} verify / {@code cleanupData}
68   * lifecycle actually runs through such an instance, and that the round-trip-fallback stays a
69   * diagnostic log rather than an error.
70   *
71   * <p>Matrix row 10 (G-c2).
72   */
73  class DbUnitExtensionSelfManagedTestCaseIT
74  {
75      private static final String TEST_TABLE = "TEST_TABLE";
76  
77      @Test
78      void testAfterTestExecution_selfManagedConnectionTestCaseWithoutTesterOverride_runsTheFullLifecycle()
79              throws Exception
80      {
81          final DatabaseEnvironment environment = DatabaseEnvironment.getInstance();
82          final DatabaseProfile profile = environment.getProfile();
83          deleteAllRowsQuietly(environment);
84          SelfManagedSample.testCase = new SelfManagedTestCase(profile);
85  
86          final Logger extensionLogger =
87                  (Logger) LoggerFactory.getLogger("org.dbunit.junit.jupiter.TesterResolver");
88          final Level originalLevel = extensionLogger.getLevel();
89          extensionLogger.setLevel(Level.DEBUG);
90          final ListAppender<ILoggingEvent> appender = new ListAppender<>();
91          appender.start();
92          extensionLogger.addAppender(appender);
93          try
94          {
95              EngineTestKit.engine("junit-jupiter")
96                      .selectors(selectClass(SelfManagedSample.class)).execute().testEvents()
97                      .assertStatistics(stats -> stats.started(1).succeeded(1));
98  
99              assertThat(appender.list)
100                     .as("A @DbUnitTestCase whose type does not override"
101                             + " getDatabaseTester()/setDatabaseTester() is the documented"
102                             + " self-managed-connection pattern - the resolution round-trip"
103                             + " failure must be a diagnostic log, never an exception.")
104                     .anyMatch(event -> event.getFormattedMessage().contains("does not round-trip"));
105 
106             final IDatabaseConnection verifyConnection = environment.getConnection();
107             assertThat(rowCount(verifyConnection, TEST_TABLE))
108                     .as("The composition test case's own cleanupData() (DELETE_ALL on its"
109                             + " internal tester) must have run and committed - the @DbUnitExpected"
110                             + " verify passing already proves configureTest/preTest/verifyData"
111                             + " ran through it end to end.")
112                     .isZero();
113         } finally
114         {
115             extensionLogger.detachAppender(appender);
116             appender.stop();
117             extensionLogger.setLevel(originalLevel);
118             deleteAllRowsQuietly(environment);
119             environment.closeConnection();
120         }
121     }
122 
123     private static int rowCount(final IDatabaseConnection connection, final String tableName)
124             throws Exception
125     {
126         try (Statement statement = connection.getConnection().createStatement();
127                 ResultSet resultSet =
128                         statement.executeQuery("SELECT COUNT(*) FROM " + tableName))
129         {
130             resultSet.next();
131             return resultSet.getInt(1);
132         }
133     }
134 
135     private static void deleteAllRowsQuietly(final DatabaseEnvironment environment)
136     {
137         try (Statement statement =
138                 environment.getConnection().getConnection().createStatement())
139         {
140             statement.execute("DELETE FROM " + TEST_TABLE);
141         } catch (final Exception e)
142         {
143             // best-effort cleanup only; a failure here must not fail the test that already ran
144         }
145     }
146 
147     @ExtendWith(DbUnitExtension.class)
148     @DbUnitConfig(databaseTesterFactory = SelfManagedSample.NoOpTesterFactory.class)
149     static class SelfManagedSample
150     {
151         @DbUnitTestCase
152         static PrepAndExpectedTestCase testCase;
153 
154         @Test
155         @DbUnitPrep("annotation-it-prep.xml")
156         @DbUnitExpected(value = "annotation-it-expected.xml",
157                 verify = @DbUnitVerifyTable(value = TEST_TABLE,
158                         include = {"COLUMN0", "COLUMN1"}))
159         void mutateTheSeededRow(final Connection connection) throws Exception
160         {
161             try (Statement statement = connection.createStatement())
162             {
163                 statement.execute("UPDATE " + TEST_TABLE
164                         + " SET COLUMN1 = 'after' WHERE COLUMN0 = 'row0'");
165             }
166         }
167 
168         /**
169          * Supplies the tester the extension's own machinery needs (it never reaches the
170          * composition test case's internal one); a NO_OP listener so nothing it does closes a
171          * connection out from under that internal lifecycle.
172          */
173         static class NoOpTesterFactory implements DatabaseTesterFactory
174         {
175             @Override
176             public IDatabaseTester getDatabaseTester() throws Exception
177             {
178                 final IDatabaseTester tester = new DefaultDatabaseTester(
179                         DatabaseEnvironment.getInstance().getConnection());
180                 tester.setOperationListener(IOperationListener.NO_OP_OPERATION_LISTENER);
181                 return tester;
182             }
183         }
184     }
185 
186     /**
187      * A composition {@link PrepAndExpectedTestCase}: every abstract method delegates to an
188      * internal {@link DefaultPrepAndExpectedTestCase} built over its own {@link JdbcDatabaseTester},
189      * with its own {@code DELETE_ALL} teardown operation. It deliberately does not override
190      * {@code getDatabaseTester()}/{@code setDatabaseTester()} - the resolution fallback the
191      * enclosing test asserts about. It does override {@code getReusableConnection()} so an
192      * injected {@code Connection} parameter still reaches the connection its own lifecycle uses.
193      */
194     static final class SelfManagedTestCase implements PrepAndExpectedTestCase
195     {
196         private final DefaultPrepAndExpectedTestCase delegate;
197 
198         SelfManagedTestCase(final DatabaseProfile profile) throws Exception
199         {
200             final JdbcDatabaseTester tester = new JdbcDatabaseTester(profile.getDriverClass(),
201                     profile.getConnectionUrl(), profile.getUser(), profile.getPassword(),
202                     profile.getSchema());
203             tester.setTearDownOperation(DatabaseOperation.DELETE_ALL);
204             delegate = new DefaultPrepAndExpectedTestCase(
205                     new org.dbunit.util.fileloader.FlatXmlDataFileLoader(), tester);
206         }
207 
208         @Override
209         public IDatabaseConnection getReusableConnection() throws Exception
210         {
211             return delegate.getReusableConnection();
212         }
213 
214         @Override
215         public void configureTest(final VerifyTableDefinition[] verifyTableDefinitions,
216                 final String[] prepDataFiles, final String[] expectedDataFiles) throws Exception
217         {
218             delegate.configureTest(verifyTableDefinitions, prepDataFiles, expectedDataFiles);
219         }
220 
221         @Override
222         public void preTest() throws Exception
223         {
224             delegate.preTest();
225         }
226 
227         @Override
228         public void preTest(final VerifyTableDefinition[] verifyTables,
229                 final String[] prepDataFiles, final String[] expectedDataFiles) throws Exception
230         {
231             delegate.preTest(verifyTables, prepDataFiles, expectedDataFiles);
232         }
233 
234         @Override
235         public Object runTest(final VerifyTableDefinition[] verifyTables,
236                 final String[] prepDataFiles, final String[] expectedDataFiles,
237                 final PrepAndExpectedTestCaseSteps testSteps) throws Exception
238         {
239             return delegate.runTest(verifyTables, prepDataFiles, expectedDataFiles, testSteps);
240         }
241 
242         @Override
243         public void postTest() throws Exception
244         {
245             delegate.postTest();
246         }
247 
248         @Override
249         public void postTest(final boolean verifyData) throws Exception
250         {
251             delegate.postTest(verifyData);
252         }
253 
254         @Override
255         public void verifyData() throws Exception
256         {
257             delegate.verifyData();
258         }
259 
260         @Override
261         public void cleanupData() throws Exception
262         {
263             delegate.cleanupData();
264         }
265 
266         @Override
267         public IDataSet getPrepDataset()
268         {
269             return delegate.getPrepDataset();
270         }
271 
272         @Override
273         public IDataSet getExpectedDataset()
274         {
275             return delegate.getExpectedDataset();
276         }
277     }
278 }