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.IDatabaseTester;
33  import org.dbunit.JdbcDatabaseTester;
34  import org.dbunit.annotation.DbUnitExpected;
35  import org.dbunit.annotation.DbUnitPrep;
36  import org.dbunit.annotation.DbUnitRowCountCheck;
37  import org.dbunit.annotation.DbUnitSetup;
38  import org.dbunit.annotation.DbUnitTearDown;
39  import org.dbunit.annotation.DbUnitTester;
40  import org.dbunit.annotation.DbUnitVerifyTable;
41  import org.dbunit.database.IDatabaseConnection;
42  import org.dbunit.database.rowcount.ClearRowCountCheckSystemProperties;
43  import org.dbunit.database.rowcount.UnexpectedRowCountException;
44  import org.dbunit.operation.DbUnitOperation;
45  import org.junit.jupiter.api.Test;
46  import org.junit.jupiter.api.extension.ExtendWith;
47  import org.junit.platform.engine.TestExecutionResult;
48  import org.junit.platform.testkit.engine.EngineTestKit;
49  import org.junit.platform.testkit.engine.Event;
50  
51  /**
52   * Real-database integration test of {@code @DbUnitRowCountCheck} driven through a genuine
53   * {@link JdbcDatabaseTester} - a fresh physical connection per {@code getConnection()} call, its
54   * own {@code DefaultOperationListener}, no {@code CachingConnectionProvider} and no fixed
55   * connection. The other row-count-check ITs use a {@code DefaultDatabaseTester} built from one
56   * connection with a {@code NO_OP} listener, so the baseline/verify connection identity and the
57   * piggyback on {@code onSetup()}'s connection are never exercised against a real fresh-connection
58   * tester end to end.
59   *
60   * <p>Matrix rows 3 & 9 with a real tester; partial G-c1.
61   */
62  @ClearRowCountCheckSystemProperties
63  class DbUnitExtensionRealTesterRowCountCheckIT
64  {
65      private static final String TEST_TABLE = "TEST_TABLE";
66      private static final String EMPTY_TABLE = "EMPTY_TABLE";
67  
68      @Test
69      void testAfterTestExecution_setupTeardownPathWithRowCountCheck_piggybacksBaselineAndVerifiesClean()
70              throws Exception
71      {
72          final DatabaseEnvironment environment = DatabaseEnvironment.getInstance();
73          runSampleExpectingSuccess(environment, SetupTeardownPathSample.class);
74  
75          final IDatabaseConnection verifyConnection = environment.getConnection();
76          assertThat(rowCount(verifyConnection, TEST_TABLE))
77                  .as("The setup/teardown path with @DbUnitRowCountCheck, driven through a real"
78                          + " JdbcDatabaseTester: baseline piggybacks on onSetup()'s connection,"
79                          + " verify re-uses that memoized connection, and"
80                          + " @DbUnitTearDown(DELETE_ALL) commits - a separate connection sees"
81                          + " TEST_TABLE empty.")
82                  .isZero();
83      }
84  
85      @Test
86      void testAfterTestExecution_prepExpectedPathWithRowCountCheck_verifiesDataAndRowCountsClean()
87              throws Exception
88      {
89          final DatabaseEnvironment environment = DatabaseEnvironment.getInstance();
90          runSampleExpectingSuccess(environment, PrepExpectedPathSample.class);
91  
92          final IDatabaseConnection verifyConnection = environment.getConnection();
93          assertThat(rowCount(verifyConnection, TEST_TABLE))
94                  .as("The prep/expected path with @DbUnitRowCountCheck, driven through a real"
95                          + " JdbcDatabaseTester: DefaultPrepAndExpectedTestCase captures the"
96                          + " baseline and verifies data + row counts on its reusable connection,"
97                          + " and @DbUnitTearDown(DELETE_ALL) commits.")
98                  .isZero();
99      }
100 
101     @Test
102     void testAfterTestExecution_testTableLeftDirtyByAnEarlierTest_stillStartsFromEmptyTables()
103             throws Exception
104     {
105         final DatabaseEnvironment environment = DatabaseEnvironment.getInstance();
106         try (Statement statement =
107                 environment.getConnection().getConnection().createStatement())
108         {
109             // What another IT class running first can leave behind: the row count check
110             // correctly reports a table whose count the lifecycle moves from the stray rows to
111             // zero, so this IT must not rely on any earlier class having cleaned up.
112             statement.execute("INSERT INTO " + TEST_TABLE + " (COLUMN0) VALUES ('stray0')");
113             statement.execute("INSERT INTO " + TEST_TABLE + " (COLUMN0) VALUES ('stray1')");
114         }
115 
116         runSampleExpectingSuccess(environment, PrepExpectedPathSample.class);
117 
118         assertThat(rowCount(environment.getConnection(), TEST_TABLE))
119                 .as("The lifecycle's teardown must leave TEST_TABLE empty, stray rows included.")
120                 .isZero();
121     }
122 
123     @Test
124     void testAfterTestExecution_rowLeakedThroughARealTester_failsProvingTheCheckActuallyRan()
125             throws Exception
126     {
127         final DatabaseEnvironment environment = DatabaseEnvironment.getInstance();
128         try
129         {
130             LeakThroughRealTesterSample.databaseTester = newJdbcDatabaseTester(environment);
131 
132             final Event failed = EngineTestKit.engine("junit-jupiter")
133                     .selectors(selectClass(LeakThroughRealTesterSample.class)).execute()
134                     .testEvents().failed().stream().findFirst().orElseThrow(
135                             () -> new AssertionError("Expected one failed test event."));
136             final Throwable reported = failed.getRequiredPayload(TestExecutionResult.class)
137                     .getThrowable().orElseThrow(
138                             () -> new AssertionError("Expected a reported throwable."));
139 
140             assertThat(reported)
141                     .as("A row left in an unlisted table must fail with"
142                             + " UnexpectedRowCountException even when the tester is a real"
143                             + " fresh-connection JdbcDatabaseTester - proving the check is"
144                             + " genuinely running, not silently no-op because a fresh"
145                             + " connection defeated the baseline.")
146                     .isInstanceOf(UnexpectedRowCountException.class);
147         } finally
148         {
149             deleteAllRowsQuietly(environment, EMPTY_TABLE);
150             environment.closeConnection();
151         }
152     }
153 
154     private void runSampleExpectingSuccess(final DatabaseEnvironment environment,
155             final Class<?> sampleClass) throws Exception
156     {
157         try
158         {
159             environment.deleteAllRows(TEST_TABLE);
160             setSampleTester(sampleClass, newJdbcDatabaseTester(environment));
161 
162             EngineTestKit.engine("junit-jupiter").selectors(selectClass(sampleClass)).execute()
163                     .testEvents().assertStatistics(stats -> stats.started(1).succeeded(1));
164         } finally
165         {
166             deleteAllRowsQuietly(environment, TEST_TABLE);
167             environment.closeConnection();
168         }
169     }
170 
171     private void setSampleTester(final Class<?> sampleClass, final IDatabaseTester tester)
172     {
173         if (sampleClass == SetupTeardownPathSample.class)
174         {
175             SetupTeardownPathSample.databaseTester = tester;
176         } else
177         {
178             PrepExpectedPathSample.databaseTester = tester;
179         }
180     }
181 
182     private static IDatabaseTester newJdbcDatabaseTester(final DatabaseEnvironment environment)
183             throws Exception
184     {
185         final DatabaseProfile profile = environment.getProfile();
186         return new JdbcDatabaseTester(profile.getDriverClass(), profile.getConnectionUrl(),
187                 profile.getUser(), profile.getPassword(), profile.getSchema());
188     }
189 
190     private static int rowCount(final IDatabaseConnection connection, final String tableName)
191             throws Exception
192     {
193         try (Statement statement = connection.getConnection().createStatement();
194                 ResultSet resultSet =
195                         statement.executeQuery("SELECT COUNT(*) FROM " + tableName))
196         {
197             resultSet.next();
198             return resultSet.getInt(1);
199         }
200     }
201 
202     private static void deleteAllRowsQuietly(final DatabaseEnvironment environment,
203             final String tableName)
204     {
205         try (Statement statement =
206                 environment.getConnection().getConnection().createStatement())
207         {
208             statement.execute("DELETE FROM " + tableName);
209         } catch (final Exception e)
210         {
211             // best-effort cleanup only; a failure here must not fail the test that already ran
212         }
213     }
214 
215     @ExtendWith(DbUnitExtension.class)
216     @ClearRowCountCheckSystemProperties
217     @DbUnitRowCountCheck
218     static class SetupTeardownPathSample
219     {
220         @DbUnitTester
221         static IDatabaseTester databaseTester;
222 
223         @Test
224         @DbUnitPrep("annotation-it-prep.xml")
225         @DbUnitTearDown(operation = DbUnitOperation.DELETE_ALL)
226         void mutateTheSeededRow(final Connection connection) throws Exception
227         {
228             try (Statement statement = connection.createStatement())
229             {
230                 statement.execute("UPDATE " + TEST_TABLE
231                         + " SET COLUMN1 = 'after' WHERE COLUMN0 = 'row0'");
232             }
233         }
234     }
235 
236     @ExtendWith(DbUnitExtension.class)
237     @ClearRowCountCheckSystemProperties
238     @DbUnitRowCountCheck
239     static class PrepExpectedPathSample
240     {
241         @DbUnitTester
242         static IDatabaseTester databaseTester;
243 
244         @Test
245         @DbUnitPrep("annotation-it-prep.xml")
246         @DbUnitExpected(value = "annotation-it-expected.xml",
247                 verify = @DbUnitVerifyTable(value = TEST_TABLE,
248                         include = {"COLUMN0", "COLUMN1"}))
249         @DbUnitTearDown(operation = DbUnitOperation.DELETE_ALL)
250         void mutateTheSeededRow(final Connection connection) throws Exception
251         {
252             try (Statement statement = connection.createStatement())
253             {
254                 statement.execute("UPDATE " + TEST_TABLE
255                         + " SET COLUMN1 = 'after' WHERE COLUMN0 = 'row0'");
256             }
257         }
258     }
259 
260     @ExtendWith(DbUnitExtension.class)
261     @ClearRowCountCheckSystemProperties
262     @DbUnitRowCountCheck
263     @DbUnitSetup(operation = DbUnitOperation.NONE)
264     static class LeakThroughRealTesterSample
265     {
266         @DbUnitTester
267         static IDatabaseTester databaseTester;
268 
269         @Test
270         void leaveARowInAnUnlistedTable(final Connection connection) throws Exception
271         {
272             try (Statement statement = connection.createStatement())
273             {
274                 statement.execute("INSERT INTO " + EMPTY_TABLE + " (COLUMN0) VALUES ('leaked')");
275             }
276         }
277     }
278 }