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_rowLeakedThroughARealTester_failsProvingTheCheckActuallyRan()
103             throws Exception
104     {
105         final DatabaseEnvironment environment = DatabaseEnvironment.getInstance();
106         try
107         {
108             LeakThroughRealTesterSample.databaseTester = newJdbcDatabaseTester(environment);
109 
110             final Event failed = EngineTestKit.engine("junit-jupiter")
111                     .selectors(selectClass(LeakThroughRealTesterSample.class)).execute()
112                     .testEvents().failed().stream().findFirst().orElseThrow(
113                             () -> new AssertionError("Expected one failed test event."));
114             final Throwable reported = failed.getRequiredPayload(TestExecutionResult.class)
115                     .getThrowable().orElseThrow(
116                             () -> new AssertionError("Expected a reported throwable."));
117 
118             assertThat(reported)
119                     .as("A row left in an unlisted table must fail with"
120                             + " UnexpectedRowCountException even when the tester is a real"
121                             + " fresh-connection JdbcDatabaseTester - proving the check is"
122                             + " genuinely running, not silently no-op because a fresh"
123                             + " connection defeated the baseline.")
124                     .isInstanceOf(UnexpectedRowCountException.class);
125         } finally
126         {
127             deleteAllRowsQuietly(environment, EMPTY_TABLE);
128             environment.closeConnection();
129         }
130     }
131 
132     private void runSampleExpectingSuccess(final DatabaseEnvironment environment,
133             final Class<?> sampleClass) throws Exception
134     {
135         try
136         {
137             setSampleTester(sampleClass, newJdbcDatabaseTester(environment));
138 
139             EngineTestKit.engine("junit-jupiter").selectors(selectClass(sampleClass)).execute()
140                     .testEvents().assertStatistics(stats -> stats.started(1).succeeded(1));
141         } finally
142         {
143             deleteAllRowsQuietly(environment, TEST_TABLE);
144             environment.closeConnection();
145         }
146     }
147 
148     private void setSampleTester(final Class<?> sampleClass, final IDatabaseTester tester)
149     {
150         if (sampleClass == SetupTeardownPathSample.class)
151         {
152             SetupTeardownPathSample.databaseTester = tester;
153         } else
154         {
155             PrepExpectedPathSample.databaseTester = tester;
156         }
157     }
158 
159     private static IDatabaseTester newJdbcDatabaseTester(final DatabaseEnvironment environment)
160             throws Exception
161     {
162         final DatabaseProfile profile = environment.getProfile();
163         return new JdbcDatabaseTester(profile.getDriverClass(), profile.getConnectionUrl(),
164                 profile.getUser(), profile.getPassword(), profile.getSchema());
165     }
166 
167     private static int rowCount(final IDatabaseConnection connection, final String tableName)
168             throws Exception
169     {
170         try (Statement statement = connection.getConnection().createStatement();
171                 ResultSet resultSet =
172                         statement.executeQuery("SELECT COUNT(*) FROM " + tableName))
173         {
174             resultSet.next();
175             return resultSet.getInt(1);
176         }
177     }
178 
179     private static void deleteAllRowsQuietly(final DatabaseEnvironment environment,
180             final String tableName)
181     {
182         try (Statement statement =
183                 environment.getConnection().getConnection().createStatement())
184         {
185             statement.execute("DELETE FROM " + tableName);
186         } catch (final Exception e)
187         {
188             // best-effort cleanup only; a failure here must not fail the test that already ran
189         }
190     }
191 
192     @ExtendWith(DbUnitExtension.class)
193     @ClearRowCountCheckSystemProperties
194     @DbUnitRowCountCheck
195     static class SetupTeardownPathSample
196     {
197         @DbUnitTester
198         static IDatabaseTester databaseTester;
199 
200         @Test
201         @DbUnitPrep("annotation-it-prep.xml")
202         @DbUnitTearDown(operation = DbUnitOperation.DELETE_ALL)
203         void mutateTheSeededRow(final Connection connection) throws Exception
204         {
205             try (Statement statement = connection.createStatement())
206             {
207                 statement.execute("UPDATE " + TEST_TABLE
208                         + " SET COLUMN1 = 'after' WHERE COLUMN0 = 'row0'");
209             }
210         }
211     }
212 
213     @ExtendWith(DbUnitExtension.class)
214     @ClearRowCountCheckSystemProperties
215     @DbUnitRowCountCheck
216     static class PrepExpectedPathSample
217     {
218         @DbUnitTester
219         static IDatabaseTester databaseTester;
220 
221         @Test
222         @DbUnitPrep("annotation-it-prep.xml")
223         @DbUnitExpected(value = "annotation-it-expected.xml",
224                 verify = @DbUnitVerifyTable(value = TEST_TABLE,
225                         include = {"COLUMN0", "COLUMN1"}))
226         @DbUnitTearDown(operation = DbUnitOperation.DELETE_ALL)
227         void mutateTheSeededRow(final Connection connection) throws Exception
228         {
229             try (Statement statement = connection.createStatement())
230             {
231                 statement.execute("UPDATE " + TEST_TABLE
232                         + " SET COLUMN1 = 'after' WHERE COLUMN0 = 'row0'");
233             }
234         }
235     }
236 
237     @ExtendWith(DbUnitExtension.class)
238     @ClearRowCountCheckSystemProperties
239     @DbUnitRowCountCheck
240     @DbUnitSetup(operation = DbUnitOperation.NONE)
241     static class LeakThroughRealTesterSample
242     {
243         @DbUnitTester
244         static IDatabaseTester databaseTester;
245 
246         @Test
247         void leaveARowInAnUnlistedTable(final Connection connection) throws Exception
248         {
249             try (Statement statement = connection.createStatement())
250             {
251                 statement.execute("INSERT INTO " + EMPTY_TABLE + " (COLUMN0) VALUES ('leaked')");
252             }
253         }
254     }
255 }