View Javadoc
1   /*
2    *
3    * The DbUnit Database Testing Framework
4    * Copyright (C)2002-2008, 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.assertThatCode;
24  import static org.assertj.core.api.Assertions.assertThatThrownBy;
25  
26  import org.dbunit.assertion.DbComparisonFailure;
27  import org.dbunit.database.IDatabaseConnection;
28  import org.dbunit.operation.DatabaseOperation;
29  import org.dbunit.util.fileloader.DataFileLoader;
30  import org.dbunit.util.fileloader.FlatXmlDataFileLoader;
31  import org.junit.jupiter.api.Test;
32  
33  /**
34   * Proves a {@link DefaultPrepAndExpectedTestCase} false-failure for tables
35   * whose generated primary key is the first column, and that
36   * {@link VerifyTableDefinition#setSortOnFilteredColumnsOnly(boolean)} fixes
37   * it.
38   * <p>
39   * By default, {@link DefaultPrepAndExpectedTestCase#verifyData} sorts the
40   * actual table (loaded from the database) using all of its native columns,
41   * identity column first, but sorts the expected table (loaded from the
42   * expected dataset file) using only the columns present in that file - which
43   * excludes the identity column, since its value cannot be known ahead of
44   * time. Excluding the identity column from comparison via
45   * {@code excludeColumns} does not help: that filter is applied after both
46   * tables are already sorted, so it never influences the sort key.
47   * <p>
48   * When production code inserts the rows in an order uncorrelated with their
49   * data - for example Hibernate reordering a batch insert - the database
50   * assigns identity values in that same uncorrelated order. The actual table
51   * then sorts by (arbitrary) insertion order while the expected table sorts by
52   * data content, so same-data rows compare against the wrong counterpart and
53   * the assertion fails despite both sides holding identical data.
54   * <p>
55   * Setting {@code sortOnFilteredColumnsOnly} true makes both tables sort by
56   * only their filtered (post exclude/include) columns instead, so the
57   * identity column never participates in the sort and same-data rows line up
58   * regardless of insertion order.
59   * <p>
60   * Tracked as GitHub issue #672 "PrepAndExpectedTestCase should only sort on
61   * filtered columns" (bug report), with companion feature request #676 "Allow
62   * PrepAndExpectedTestCase to sort only on filtered columns instead of all
63   * columns" - this class's fix.
64   *
65   * @author Jeff Jensen jeffjensen AT users.sourceforge.net
66   * @since 3.5.0
67   */
68  class DefaultPrepAndExpectedTestCaseGeneratedIdRowOrderIT
69  {
70      private static final String IDENTITY_TABLE_NAME = "IDENTITY_TABLE";
71      private static final String IDENTITY_TABLE_ID_COLUMN =
72              "IDENTITY_TABLE_ID";
73  
74      private static final String PREP_DATA_FILE_NAME =
75              "/xml/generatedIdRowOrderPrep.xml";
76      private static final String EXPECTED_MATCH_DATA_FILE_NAME =
77              "/xml/generatedIdRowOrderExpectedMatch.xml";
78      private static final String EXPECTED_MISMATCH_DATA_FILE_NAME =
79              "/xml/generatedIdRowOrderExpectedMismatch.xml";
80  
81      private final DataFileLoader dataFileLoader = new FlatXmlDataFileLoader();
82  
83      @Test
84      void testVerifyData_defaultSortsAllColumns_rowsInsertedInDifferentOrderButSameData_throwsDbComparisonFailure()
85              throws Exception
86      {
87          final DefaultPrepAndExpectedTestCase tc = makeTestCase();
88          tc.configureTest(makeVerifyTableDefinitions(false),
89                  new String[] {PREP_DATA_FILE_NAME},
90                  new String[] {EXPECTED_MATCH_DATA_FILE_NAME});
91          tc.preTest();
92  
93          // Documents the known defect described in the class Javadoc: both
94          // sides hold the same two rows, only their insertion/generated-ID
95          // order differs. The default (sortOnFilteredColumnsOnly=false)
96          // preserves this historical, backward-compatible behavior.
97          assertThatThrownBy(() -> tc.postTest())
98                  .as("Expected the default sort-all-columns behavior to still"
99                          + " reproduce the known generated-ID row order defect"
100                         + " as a DbComparisonFailure; if this fails, the"
101                         + " default may have changed - see this class's"
102                         + " Javadoc.")
103                 .isInstanceOf(DbComparisonFailure.class);
104     }
105 
106     @Test
107     void testVerifyData_sortOnFilteredColumnsOnly_rowsInsertedInDifferentOrderButSameData_doesNotThrow()
108             throws Exception
109     {
110         final DefaultPrepAndExpectedTestCase tc = makeTestCase();
111         tc.configureTest(makeVerifyTableDefinitions(true),
112                 new String[] {PREP_DATA_FILE_NAME},
113                 new String[] {EXPECTED_MATCH_DATA_FILE_NAME});
114         tc.preTest();
115 
116         // Opting in to sortOnFilteredColumnsOnly removes the identity column
117         // from the sort key on both sides, so same-data rows line up
118         // regardless of insertion order and the comparison passes.
119         assertThatCode(() -> tc.postTest())
120                 .as("Expected sortOnFilteredColumnsOnly=true to fix the"
121                         + " generated-ID row order defect so tc.postTest()"
122                         + " does not throw, but it did.")
123                 .doesNotThrowAnyException();
124     }
125 
126     @Test
127     void testVerifyData_sortOnFilteredColumnsOnly_rowsWithGenuineDataMismatch_throwsDbComparisonFailure()
128             throws Exception
129     {
130         final DefaultPrepAndExpectedTestCase tc = makeTestCase();
131         tc.configureTest(makeVerifyTableDefinitions(true),
132                 new String[] {PREP_DATA_FILE_NAME},
133                 new String[] {EXPECTED_MISMATCH_DATA_FILE_NAME});
134         tc.preTest();
135 
136         // Control case: sortOnFilteredColumnsOnly=true must not mask a
137         // genuine data mismatch (not just a row order difference).
138         assertThatThrownBy(() -> tc.postTest())
139                 .as("Expected tc.postTest() to throw DbComparisonFailure for"
140                         + " genuinely mismatched data even with"
141                         + " sortOnFilteredColumnsOnly=true, but it didn't.")
142                 .isInstanceOf(DbComparisonFailure.class);
143     }
144 
145     private VerifyTableDefinition[] makeVerifyTableDefinitions(
146             final boolean sortOnFilteredColumnsOnly)
147     {
148         final VerifyTableDefinition identityTable = new VerifyTableDefinition(
149                 IDENTITY_TABLE_NAME, new String[] {IDENTITY_TABLE_ID_COLUMN});
150         identityTable
151                 .setSortOnFilteredColumnsOnly(sortOnFilteredColumnsOnly);
152         return new VerifyTableDefinition[] {identityTable};
153     }
154 
155     private DefaultPrepAndExpectedTestCase makeTestCase() throws Exception
156     {
157         return new DefaultPrepAndExpectedTestCase(dataFileLoader,
158                 makeDatabaseTester());
159     }
160 
161     private IDatabaseTester makeDatabaseTester() throws Exception
162     {
163         final DatabaseEnvironment dbEnv = DatabaseEnvironment.getInstance();
164         final IDatabaseConnection connection = dbEnv.getConnection();
165         final IDatabaseTester databaseTester =
166                 new DefaultDatabaseTester(connection);
167         databaseTester.setTearDownOperation(DatabaseOperation.DELETE_ALL);
168         return databaseTester;
169     }
170 }