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  import static org.assertj.core.api.Assertions.assertThatCode;
25  import static org.assertj.core.api.Assertions.catchThrowable;
26  
27  import java.sql.Statement;
28  
29  import org.dbunit.database.DatabaseConfig;
30  import org.dbunit.database.IDatabaseConnection;
31  import org.dbunit.database.rowcount.ClearRowCountCheckSystemProperties;
32  import org.dbunit.database.rowcount.RowCountDifference;
33  import org.dbunit.database.rowcount.UnexpectedRowCountException;
34  import org.dbunit.dataset.Column;
35  import org.dbunit.dataset.DefaultDataSet;
36  import org.dbunit.dataset.DefaultTable;
37  import org.dbunit.dataset.datatype.DataType;
38  import org.dbunit.operation.DatabaseOperation;
39  import org.dbunit.util.fileloader.DataFileLoader;
40  import org.dbunit.util.fileloader.FlatXmlDataFileLoader;
41  import org.junit.jupiter.api.AfterEach;
42  import org.junit.jupiter.api.BeforeEach;
43  import org.junit.jupiter.api.Test;
44  
45  /**
46   * Real-database integration test of the row count check wired into
47   * {@link DefaultPrepAndExpectedTestCase}: a leaked row in a table the test never lists, a
48   * reference table wrongly listed for cleanup, and the exclude list silencing a legitimate
49   * case of either. Deltas are asserted rather than absolute counts, so the test does not
50   * depend on {@code EMPTY_TABLE}/{@code SECOND_TABLE} starting genuinely empty.
51   */
52  @ClearRowCountCheckSystemProperties
53  class DefaultPrepAndExpectedTestCaseRowCountCheckIT
54  {
55      private static final String EMPTY_TABLE = "EMPTY_TABLE";
56      private static final String SECOND_TABLE = "SECOND_TABLE";
57  
58      private final DataFileLoader dataFileLoader = new FlatXmlDataFileLoader();
59  
60      private IDatabaseConnection connection;
61      private DefaultPrepAndExpectedTestCase tc;
62  
63      @BeforeEach
64      void setUp() throws Exception
65      {
66          final DatabaseEnvironment dbEnv = DatabaseEnvironment.getInstance();
67          connection = dbEnv.getConnection();
68          connection.getConfig().setFeature(DatabaseConfig.FEATURE_ROW_COUNT_CHECK, true);
69  
70          final IDatabaseTester databaseTester = new DefaultDatabaseTester(connection);
71          databaseTester.setTearDownOperation(DatabaseOperation.DELETE_ALL);
72          tc = new DefaultPrepAndExpectedTestCase(dataFileLoader, databaseTester);
73      }
74  
75      @AfterEach
76      void cleanUp() throws Exception
77      {
78          final DatabaseEnvironment dbEnv = DatabaseEnvironment.getInstance();
79          final IDatabaseConnection cleanupConnection = dbEnv.getConnection();
80          try
81          {
82              deleteAllRowsQuietly(cleanupConnection, EMPTY_TABLE);
83              deleteAllRowsQuietly(cleanupConnection, SECOND_TABLE);
84          } finally
85          {
86              closeQuietly(cleanupConnection);
87          }
88      }
89  
90      @Test
91      void testPostTest_rowLeakedIntoUnlistedTable_throwsUnexpectedRowCountExceptionNamingIt()
92              throws Exception
93      {
94          tc.preTest();
95  
96          // the code under test wrote to a table the developer forgot to list for teardown
97          insertRow(EMPTY_TABLE);
98  
99          final Throwable thrown = catchThrowable(() -> tc.postTest());
100 
101         assertThat(thrown)
102                 .as("A row left behind in a table absent from prep/expected must fail the"
103                         + " test with UnexpectedRowCountException.")
104                 .isInstanceOf(UnexpectedRowCountException.class);
105         assertThat(differenceFor((UnexpectedRowCountException) thrown, EMPTY_TABLE).getDelta())
106                 .as("A row was left behind, so the delta must be positive; not asserting the"
107                         + " exact value, since EMPTY_TABLE is shared with other IT classes"
108                         + " and may not start genuinely empty.")
109                 .isPositive();
110     }
111 
112     @Test
113     void testPostTest_referenceTableWronglyListedForCleanup_throwsUnexpectedRowCountExceptionWithNegativeDelta()
114             throws Exception
115     {
116         // pre-existing reference data, seeded before this test's baseline is captured
117         insertRow(SECOND_TABLE);
118 
119         // wrongly listing SECOND_TABLE for cleanup - its rows get wiped, by CLEAN_INSERT
120         // during setupData() here, or by DELETE_ALL during cleanupData() otherwise
121         final Column[] columns = {new Column("COLUMN0", DataType.VARCHAR)};
122         tc.setPrepDs(new DefaultDataSet(new DefaultTable(SECOND_TABLE, columns)));
123 
124         tc.preTest();
125 
126         final Throwable thrown = catchThrowable(() -> tc.postTest());
127 
128         assertThat(thrown)
129                 .as("A reference table wrongly listed for cleanup must fail the test with"
130                         + " UnexpectedRowCountException.")
131                 .isInstanceOf(UnexpectedRowCountException.class);
132         assertThat(differenceFor((UnexpectedRowCountException) thrown, SECOND_TABLE).getDelta())
133                 .as("Pre-existing rows were wiped, so the delta must be negative; not"
134                         + " asserting the exact value, since SECOND_TABLE is shared with"
135                         + " other IT classes and may carry more than this test's own row.")
136                 .isNegative();
137     }
138 
139     @Test
140     void testPostTest_leakedRowInExcludedTable_doesNotThrow() throws Exception
141     {
142         connection.getConfig().setProperty(
143                 DatabaseConfig.PROPERTY_ROW_COUNT_CHECK_EXCLUDE_TABLES,
144                 new String[] {EMPTY_TABLE});
145 
146         tc.preTest();
147         insertRow(EMPTY_TABLE);
148 
149         assertThatCode(() -> tc.postTest())
150                 .as("A table matching an exclude pattern must never be reported, even"
151                         + " though its count actually changed.")
152                 .doesNotThrowAnyException();
153     }
154 
155     private RowCountDifference differenceFor(final UnexpectedRowCountException exception,
156             final String tableName)
157     {
158         return exception.getDifferences().stream()
159                 .filter(difference -> difference.getTableName().equalsIgnoreCase(tableName))
160                 .findFirst()
161                 .orElseThrow(() -> new AssertionError(
162                         "No difference reported for table '" + tableName + "': "
163                                 + exception.getDifferences()));
164     }
165 
166     private void insertRow(final String tableName) throws Exception
167     {
168         try (Statement statement = connection.getConnection().createStatement())
169         {
170             statement.execute(
171                     "INSERT INTO " + tableName + " (COLUMN0) VALUES ('rowCountCheckIT')");
172         }
173     }
174 
175     private static void deleteAllRowsQuietly(final IDatabaseConnection connection,
176             final String tableName)
177     {
178         try (Statement statement = connection.getConnection().createStatement())
179         {
180             statement.execute("DELETE FROM " + tableName);
181         } catch (final Exception e)
182         {
183             // best-effort cleanup only; a failure here must not fail the test that already ran
184         }
185     }
186 
187     private static void closeQuietly(final IDatabaseConnection connection)
188     {
189         try
190         {
191             connection.close();
192         } catch (final Exception e)
193         {
194             // best-effort cleanup only; a failure here must not fail the test that already ran
195         }
196     }
197 }