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 }