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;
22  
23  import static org.assertj.core.api.Assertions.assertThat;
24  
25  import java.util.ArrayList;
26  import java.util.HashSet;
27  import java.util.List;
28  
29  import org.dbunit.database.CachingConnectionProvider;
30  import org.dbunit.database.IDatabaseConnection;
31  import org.dbunit.dataset.IDataSet;
32  import org.dbunit.operation.DatabaseOperation;
33  import org.dbunit.util.fileloader.DataFileLoader;
34  import org.dbunit.util.fileloader.FlatXmlDataFileLoader;
35  import org.junit.jupiter.api.BeforeEach;
36  import org.junit.jupiter.api.Test;
37  
38  /**
39   * Integration tests proving that a {@link CachingConnectionProvider}, shared across the fresh
40   * {@link IDatabaseTester} instances a test harness builds for each test method (exactly as
41   * {@link DatabaseTestCase} already does - its {@code tester} field is reset to <code>null</code>
42   * after every {@link DatabaseTestCase#tearDown()}, so {@link DatabaseTestCase#newDatabaseTester()}
43   * runs again on the next test), delivers the cross-test-method connection reuse issue #799 asks
44   * for - and only when paired with a non-closing {@link IOperationListener}, staying fully
45   * backward compatible otherwise.
46   * <p>
47   * Also proves {@link DefaultPrepAndExpectedTestCase} - which manages its own connection lifecycle
48   * rather than delegating to a listener (see issue #800) - only joins that cross-test reuse when
49   * {@link DefaultPrepAndExpectedTestCase#setCloseConnectionAfterTest(boolean)} is set to false;
50   * left at its default, it must not close a connection a shared provider is still using for
51   * other test methods (issue #801 code review feedback).
52   *
53   * @since 3.4.0
54   */
55  class DatabaseTesterConnectionReuseIT
56  {
57      private DatabaseProfile profile;
58  
59      @BeforeEach
60      void setUp() throws Exception
61      {
62          profile = DatabaseEnvironment.getInstance().getProfile();
63      }
64  
65      @Test
66      void testOnSetupAndOnTearDown_acrossFreshTesterInstancesSharingAProviderAndNoOpListener_reuseOneConnection()
67              throws Exception
68      {
69          final CachingConnectionProvider sharedProvider = new CachingConnectionProvider();
70          final List<IDatabaseConnection> connectionsUsed = new ArrayList<>();
71          try
72          {
73              for (int simulatedTestMethod = 0; simulatedTestMethod < 3; simulatedTestMethod++)
74              {
75                  final IDatabaseTester tester = newSharedProviderTester(sharedProvider);
76                  tester.setOperationListener(IOperationListener.NO_OP_OPERATION_LISTENER);
77                  tester.setSetUpOperation(capturingOperation(connectionsUsed));
78                  tester.setTearDownOperation(capturingOperation(connectionsUsed));
79  
80                  tester.onSetup();
81                  tester.onTearDown();
82              }
83  
84              assertThat(connectionsUsed)
85                      .as("Precondition: setUp+tearDown across 3 simulated test methods must have "
86                              + "executed an operation, and therefore captured a connection, 6 times.")
87                      .hasSize(6);
88              assertThat(new HashSet<>(connectionsUsed))
89                      .as("Every onSetup()/onTearDown() call across 3 simulated test methods - each "
90                              + "building its own fresh IDatabaseTester, exactly as DatabaseTestCase "
91                              + "does per test - must share the one connection cached by the "
92                              + "CachingConnectionProvider they all point at, since a "
93                              + "NO_OP_OPERATION_LISTENER keeps it from ever being closed between "
94                              + "calls.")
95                      .hasSize(1);
96          } finally
97          {
98              sharedProvider.close();
99          }
100     }
101 
102     @Test
103     void testOnSetupAndOnTearDown_acrossFreshTesterInstancesSharingAProviderWithDefaultListener_staysOptInAndCreatesFreshConnectionsEveryTime()
104             throws Exception
105     {
106         final CachingConnectionProvider sharedProvider = new CachingConnectionProvider();
107         final List<IDatabaseConnection> connectionsUsed = new ArrayList<>();
108         try
109         {
110             for (int simulatedTestMethod = 0; simulatedTestMethod < 2; simulatedTestMethod++)
111             {
112                 final IDatabaseTester tester = newSharedProviderTester(sharedProvider);
113                 // Deliberately not overriding the default DefaultOperationListener, which closes
114                 // the connection after every onSetup()/onTearDown() call.
115                 tester.setSetUpOperation(capturingOperation(connectionsUsed));
116                 tester.setTearDownOperation(capturingOperation(connectionsUsed));
117 
118                 tester.onSetup();
119                 tester.onTearDown();
120             }
121 
122             assertThat(connectionsUsed)
123                     .as("Precondition: setUp+tearDown across 2 simulated test methods must have "
124                             + "captured a connection 4 times.")
125                     .hasSize(4);
126             assertThat(new HashSet<>(connectionsUsed))
127                     .as("Opting into a CachingConnectionProvider without also switching away from "
128                             + "the library's default closing IOperationListener must stay fully "
129                             + "backward compatible: every call still observes a freshly "
130                             + "(re)created connection, exactly like not using a provider at all.")
131                     .hasSize(4);
132         } finally
133         {
134             sharedProvider.close();
135         }
136     }
137 
138     @Test
139     void testDefaultPrepAndExpectedTestCase_acrossFreshInstancesSharingAProviderWithCloseDisabled_reusesOneConnection()
140             throws Exception
141     {
142         final CachingConnectionProvider sharedProvider = new CachingConnectionProvider();
143         final List<IDatabaseConnection> connectionsUsed = new ArrayList<>();
144         try
145         {
146             for (int simulatedTestMethod = 0; simulatedTestMethod < 3; simulatedTestMethod++)
147             {
148                 final IDatabaseTester tester = newSharedProviderTester(sharedProvider);
149                 final DefaultPrepAndExpectedTestCase tc = newTestCase(tester);
150                 tc.setCloseConnectionAfterTest(false);
151 
152                 tc.configureTest(new VerifyTableDefinition[] {}, new String[] {},
153                         new String[] {});
154                 tc.preTest();
155                 tc.postTest();
156 
157                 connectionsUsed.add(tester.getConnection());
158             }
159 
160             assertThat(new HashSet<>(connectionsUsed))
161                     .as("With closing disabled, DefaultPrepAndExpectedTestCase must not close the "
162                             + "connection a shared CachingConnectionProvider still has cached, so "
163                             + "3 simulated test methods - each building its own fresh tester and "
164                             + "test case pointed at the same provider - must all observe the same "
165                             + "underlying connection (issue #800/#801).")
166                     .hasSize(1);
167         } finally
168         {
169             sharedProvider.close();
170         }
171     }
172 
173     @Test
174     void testDefaultPrepAndExpectedTestCase_acrossFreshInstancesSharingAProviderWithDefaultConfiguration_createsFreshConnectionsEveryTime()
175             throws Exception
176     {
177         final CachingConnectionProvider sharedProvider = new CachingConnectionProvider();
178         final List<IDatabaseConnection> connectionsUsed = new ArrayList<>();
179         try
180         {
181             for (int simulatedTestMethod = 0; simulatedTestMethod < 2; simulatedTestMethod++)
182             {
183                 final IDatabaseTester tester = newSharedProviderTester(sharedProvider);
184                 final DefaultPrepAndExpectedTestCase tc = newTestCase(tester);
185                 // Deliberately not calling setCloseConnectionAfterTest(false).
186 
187                 tc.configureTest(new VerifyTableDefinition[] {}, new String[] {},
188                         new String[] {});
189                 tc.preTest();
190                 tc.postTest();
191 
192                 connectionsUsed.add(tester.getConnection());
193             }
194 
195             assertThat(new HashSet<>(connectionsUsed))
196                     .as("Left at its default, DefaultPrepAndExpectedTestCase must stay fully "
197                             + "backward compatible: cleanupData() closes the connection it used, "
198                             + "so the CachingConnectionProvider must hand back a freshly "
199                             + "(re)created one to the next simulated test method, exactly like not "
200                             + "sharing a provider at all.")
201                     .hasSize(2);
202         } finally
203         {
204             sharedProvider.close();
205         }
206     }
207 
208     @Test
209     void testDefaultPrepAndExpectedTestCase_reusedAcrossTestMethodsWithCloseDisabled_keepsReusingItsOneOpenConnection()
210             throws Exception
211     {
212         final CachingConnectionProvider provider = new CachingConnectionProvider();
213         final IDatabaseTester tester = newSharedProviderTester(provider);
214         tester.setTearDownOperation(DatabaseOperation.DELETE_ALL);
215         final DefaultPrepAndExpectedTestCase tc = newCloseDisabledTestCase(tester);
216         final List<IDatabaseConnection> connectionsSeen = new ArrayList<>();
217         try
218         {
219             for (int simulatedTestMethod = 0; simulatedTestMethod < 3; simulatedTestMethod++)
220             {
221                 runOneSimulatedTestMethod(tc);
222 
223                 final IDatabaseConnection connection = tester.getConnection();
224                 connectionsSeen.add(connection);
225                 assertThat(connection.getConnection().isClosed())
226                         .as("closeConnectionAfterTest=false: the connection must still be"
227                                 + " open for the next simulated test method to reuse.")
228                         .isFalse();
229             }
230 
231             assertThat(new HashSet<>(connectionsSeen))
232                     .as("One DefaultPrepAndExpectedTestCase reused across test methods, backed"
233                             + " by a CachingConnectionProvider, must run every method against"
234                             + " the one cached connection while it stays alive.")
235                     .hasSize(1);
236         } finally
237         {
238             provider.close();
239         }
240     }
241 
242     @Test
243     void testDefaultPrepAndExpectedTestCase_reusedWithCloseDisabled_whenItsCachedConnectionDiesBetweenTestMethods_replacesItRatherThanReusingTheDeadOne()
244             throws Exception
245     {
246         final CachingConnectionProvider provider = new CachingConnectionProvider();
247         final IDatabaseTester tester = newSharedProviderTester(provider);
248         tester.setTearDownOperation(DatabaseOperation.DELETE_ALL);
249         final DefaultPrepAndExpectedTestCase tc = newCloseDisabledTestCase(tester);
250         try
251         {
252             // First simulated test method: acquires and pins a connection.
253             runOneSimulatedTestMethod(tc);
254             final IDatabaseConnection firstConnection = tester.getConnection();
255 
256             // The connection pool or the database server drops that connection
257             // between test methods - max lifetime, an idle-in-transaction
258             // timeout, a bounced application context, and so on.
259             firstConnection.getConnection().close();
260 
261             // Second simulated test method on the SAME instance must notice the
262             // dead connection it pinned and acquire a live replacement, not fail
263             // setupData()'s CLEAN_INSERT - and then cleanupData()'s tear down
264             // operation - on the connection it can no longer use.
265             runOneSimulatedTestMethod(tc);
266 
267             final IDatabaseConnection secondConnection = tester.getConnection();
268             assertThat(secondConnection.getConnection().isClosed())
269                     .as("The reused test case must have replaced the connection killed"
270                             + " between test methods, not kept handing back the dead one.")
271                     .isFalse();
272             assertThat(secondConnection)
273                     .as("A fresh connection must have been acquired from the"
274                             + " CachingConnectionProvider once the first one died.")
275                     .isNotSameAs(firstConnection);
276         } finally
277         {
278             provider.close();
279         }
280     }
281 
282     private DefaultPrepAndExpectedTestCase newTestCase(final IDatabaseTester tester)
283     {
284         final DataFileLoader dataFileLoader = new FlatXmlDataFileLoader();
285         return new DefaultPrepAndExpectedTestCase(dataFileLoader, tester);
286     }
287 
288     private DefaultPrepAndExpectedTestCase newCloseDisabledTestCase(final IDatabaseTester tester)
289     {
290         final DefaultPrepAndExpectedTestCase tc = newTestCase(tester);
291         tc.setCloseConnectionAfterTest(false);
292         return tc;
293     }
294 
295     private static void runOneSimulatedTestMethod(final DefaultPrepAndExpectedTestCase tc)
296             throws Exception
297     {
298         tc.runTest(new VerifyTableDefinition[] {}, new String[] {}, new String[] {},
299                 () -> null);
300     }
301 
302     private IDatabaseTester newSharedProviderTester(final CachingConnectionProvider sharedProvider)
303             throws Exception
304     {
305         return new JdbcDatabaseTester(profile.getDriverClass(), profile.getConnectionUrl(),
306                 profile.getUser(), profile.getPassword(), profile.getSchema(), sharedProvider);
307     }
308 
309     /**
310      * Records the connection passed to it by {@code AbstractDatabaseTester.executeOperation()},
311      * which only invokes {@link IDatabaseTester#getConnection()} - and therefore only calls this -
312      * when the configured operation is not {@link DatabaseOperation#NONE}.
313      */
314     private static DatabaseOperation capturingOperation(final List<IDatabaseConnection> capturedInto)
315     {
316         return new DatabaseOperation()
317         {
318             @Override
319             public void execute(final IDatabaseConnection connection, final IDataSet dataSet)
320             {
321                 capturedInto.add(connection);
322             }
323         };
324     }
325 }