1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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.Connection;
28 import java.sql.ResultSet;
29 import java.sql.SQLException;
30 import java.sql.Statement;
31 import java.util.LinkedHashMap;
32 import java.util.Map;
33 import java.util.concurrent.atomic.AtomicInteger;
34
35 import org.dbunit.database.DatabaseConnection;
36 import org.dbunit.database.IDatabaseConnection;
37 import org.dbunit.operation.DatabaseOperation;
38 import org.dbunit.util.fileloader.DataFileLoader;
39 import org.dbunit.util.fileloader.FlatXmlDataFileLoader;
40 import org.junit.jupiter.api.AfterEach;
41 import org.junit.jupiter.api.BeforeEach;
42 import org.junit.jupiter.api.Test;
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 class MultiDataSourcePrepAndExpectedTestCaseIT
59 {
60 private static final String CATALOG = "catalog";
61
62 private static final String ORDERS = "orders";
63
64 private static final String INVENTORY = "inventory";
65
66 private static final String CATALOG_PREP = "/xml/multiDataSourceCatalogPrep.xml";
67
68 private static final String CATALOG_EXPECTED = "/xml/multiDataSourceCatalogExpected.xml";
69
70 private static final String ORDERS_PREP = "/xml/multiDataSourceOrdersPrep.xml";
71
72 private static final String ORDERS_EXPECTED = "/xml/multiDataSourceOrdersExpected.xml";
73
74 private static final String ORDERS_EXPECTED_MISMATCH =
75 "/xml/multiDataSourceOrdersExpectedMismatch.xml";
76
77 private static final String INVENTORY_PREP = "/xml/multiDataSourceInventoryPrep.xml";
78
79 private static final String INVENTORY_EXPECTED = "/xml/multiDataSourceInventoryExpected.xml";
80
81 private static final String INVENTORY_EXPECTED_MISMATCH =
82 "/xml/multiDataSourceInventoryExpectedMismatch.xml";
83
84 private static final VerifyTableDefinition PRODUCT_TABLE =
85 new VerifyTableDefinition("PRODUCT", new String[0]);
86
87
88
89
90 private static final VerifyTableDefinition PURCHASE_ORDER_TABLE = new VerifyTableDefinition(
91 "PURCHASE_ORDER", new String[] {"ID"}, null, null, true);
92
93 private static final VerifyTableDefinition STOCK_TABLE =
94 new VerifyTableDefinition("STOCK", new String[0]);
95
96 private static final AtomicInteger RUN_COUNTER = new AtomicInteger();
97
98 private final DataFileLoader dataFileLoader = new FlatXmlDataFileLoader();
99
100 private String catalogDbName;
101
102 private String ordersDbName;
103
104 private String inventoryDbName;
105
106 private Connection catalogJdbcConnection;
107
108 private Connection ordersJdbcConnection;
109
110 private Connection inventoryJdbcConnection;
111
112 private MultiDataSourcePrepAndExpectedTestCase testCase;
113
114 @BeforeEach
115 void setUp() throws Exception
116 {
117 final int run = RUN_COUNTER.incrementAndGet();
118 catalogDbName = "mem:multiDsCatalog" + run;
119 ordersDbName = "mem:multiDsOrders" + run;
120 inventoryDbName = "mem:multiDsInventory" + run;
121
122 catalogJdbcConnection = HypersonicEnvironment.createJdbcConnection(catalogDbName);
123 ordersJdbcConnection = HypersonicEnvironment.createJdbcConnection(ordersDbName);
124 inventoryJdbcConnection = HypersonicEnvironment.createJdbcConnection(inventoryDbName);
125
126 createSchema(catalogJdbcConnection,
127 "CREATE TABLE PRODUCT (SKU VARCHAR(20) PRIMARY KEY, NAME VARCHAR(60),"
128 + " UNIT_PRICE DECIMAL(10,2), UNITS_SOLD INTEGER)");
129 createSchema(ordersJdbcConnection,
130 "CREATE TABLE PURCHASE_ORDER (ID INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY"
131 + " KEY, CUSTOMER_ID BIGINT, SKU VARCHAR(20), QUANTITY INTEGER, TOTAL"
132 + " DECIMAL(10,2))");
133 createSchema(inventoryJdbcConnection,
134 "CREATE TABLE STOCK (SKU VARCHAR(20) PRIMARY KEY, QTY_ON_HAND INTEGER)");
135
136 testCase = MultiDataSourcePrepAndExpectedTestCase.forTesters(dataFileLoader)
137 .add(CATALOG, testerFor(catalogJdbcConnection))
138 .add(ORDERS, testerFor(ordersJdbcConnection))
139 .add(INVENTORY, testerFor(inventoryJdbcConnection));
140 }
141
142 @AfterEach
143 void tearDown()
144 {
145 closeQuietly(catalogJdbcConnection);
146 closeQuietly(ordersJdbcConnection);
147 closeQuietly(inventoryJdbcConnection);
148 }
149
150 @Test
151 void testRunTest_matchingPrepAndExpectedInEveryDataSource_doesNotThrow()
152 {
153 final Map<String, PrepAndExpectedTestData> data = new LinkedHashMap<>();
154 data.put(CATALOG, catalogData(CATALOG_EXPECTED));
155 data.put(ORDERS, ordersData(ORDERS_EXPECTED));
156 data.put(INVENTORY, inventoryData(INVENTORY_EXPECTED));
157
158 assertThatCode(() -> testCase.runTest(data, this::placeOrder))
159 .as("Matching prep and expected data in every data source must not throw.")
160 .doesNotThrowAnyException();
161 }
162
163 @Test
164 void testRunTest_expectedMismatchInTwoDataSources_throwsAssertionErrorNamingBoth()
165 {
166 final Map<String, PrepAndExpectedTestData> data = new LinkedHashMap<>();
167 data.put(CATALOG, catalogData(CATALOG_EXPECTED));
168 data.put(ORDERS, ordersData(ORDERS_EXPECTED_MISMATCH));
169 data.put(INVENTORY, inventoryData(INVENTORY_EXPECTED_MISMATCH));
170
171 final Throwable thrown = catchThrowable(() -> testCase.runTest(data, this::placeOrder));
172
173 assertThat(thrown)
174 .as("A mismatch in two data sources must surface as one"
175 + " MultiDataSourceAssertionError, not stop at the first.")
176 .isInstanceOf(MultiDataSourceAssertionError.class);
177 assertThat(thrown.getMessage())
178 .as("The aggregate message must name both mismatched data sources.")
179 .contains(ORDERS).contains(INVENTORY);
180 }
181
182 @Test
183 void testRunTest_dataSourceOmittedFromTheRow_itsConnectionIsNeverOpened() throws Exception
184 {
185 testCase = MultiDataSourcePrepAndExpectedTestCase.forTesters(dataFileLoader)
186 .add(CATALOG, testerFor(catalogJdbcConnection))
187 .add(ORDERS, testerFor(ordersJdbcConnection))
188 .add(INVENTORY, new ThrowingDatabaseTester());
189
190 final Map<String, PrepAndExpectedTestData> data = new LinkedHashMap<>();
191 data.put(CATALOG, catalogData(CATALOG_EXPECTED));
192 data.put(ORDERS, ordersData(ORDERS_EXPECTED));
193
194
195 assertThatCode(() -> testCase.runTest(data, this::placeOrder))
196 .as("A data source omitted from the data map must never have its connection"
197 + " opened, even when opening it would throw.")
198 .doesNotThrowAnyException();
199 }
200
201 @Test
202 void testRunTest_success_cleansAndClosesEveryConnection() throws Exception
203 {
204 final Map<String, PrepAndExpectedTestData> data = new LinkedHashMap<>();
205 data.put(CATALOG, catalogData(CATALOG_EXPECTED));
206 data.put(ORDERS, ordersData(ORDERS_EXPECTED));
207 data.put(INVENTORY, inventoryData(INVENTORY_EXPECTED));
208
209 testCase.runTest(data, this::placeOrder);
210
211 assertThat(catalogJdbcConnection.isClosed())
212 .as("catalog's connection must be closed after a successful runTest(...).")
213 .isTrue();
214 assertThat(ordersJdbcConnection.isClosed())
215 .as("orders' connection must be closed after a successful runTest(...).")
216 .isTrue();
217 assertThat(inventoryJdbcConnection.isClosed())
218 .as("inventory's connection must be closed after a successful runTest(...).")
219 .isTrue();
220
221 assertRowCount(catalogDbName, "PRODUCT", 0);
222 assertRowCount(ordersDbName, "PURCHASE_ORDER", 0);
223 assertRowCount(inventoryDbName, "STOCK", 0);
224 }
225
226
227
228
229
230 private Object placeOrder() throws Exception
231 {
232 try (Statement statement = catalogJdbcConnection.createStatement())
233 {
234 statement.executeUpdate(
235 "UPDATE PRODUCT SET UNITS_SOLD = UNITS_SOLD + 3 WHERE SKU = 'WIDGET-1'");
236 }
237 try (Statement statement = ordersJdbcConnection.createStatement())
238 {
239 statement.executeUpdate("INSERT INTO PURCHASE_ORDER (CUSTOMER_ID, SKU, QUANTITY,"
240 + " TOTAL) VALUES (42, 'WIDGET-1', 3, 29.97)");
241 }
242 try (Statement statement = inventoryJdbcConnection.createStatement())
243 {
244 statement.executeUpdate(
245 "UPDATE STOCK SET QTY_ON_HAND = QTY_ON_HAND - 3 WHERE SKU = 'WIDGET-1'");
246 }
247 return null;
248 }
249
250 private static PrepAndExpectedTestData catalogData(final String expectedFile)
251 {
252 return new PrepAndExpectedTestData(new VerifyTableDefinition[] {PRODUCT_TABLE},
253 new String[] {CATALOG_PREP}, new String[] {expectedFile});
254 }
255
256 private static PrepAndExpectedTestData ordersData(final String expectedFile)
257 {
258 return new PrepAndExpectedTestData(new VerifyTableDefinition[] {PURCHASE_ORDER_TABLE},
259 new String[] {ORDERS_PREP}, new String[] {expectedFile});
260 }
261
262 private static PrepAndExpectedTestData inventoryData(final String expectedFile)
263 {
264 return new PrepAndExpectedTestData(new VerifyTableDefinition[] {STOCK_TABLE},
265 new String[] {INVENTORY_PREP}, new String[] {expectedFile});
266 }
267
268 private static void createSchema(final Connection connection, final String ddl)
269 throws Exception
270 {
271 DdlExecutor.executeSql(connection, ddl);
272 }
273
274 private static IDatabaseTester testerFor(final Connection connection) throws Exception
275 {
276 final IDatabaseConnection databaseConnection = new DatabaseConnection(connection);
277 final IDatabaseTester tester = new DefaultDatabaseTester(databaseConnection);
278 tester.setTearDownOperation(DatabaseOperation.DELETE_ALL);
279 return tester;
280 }
281
282 private static void assertRowCount(final String dbName, final String tableName,
283 final int expectedRows) throws Exception
284 {
285 try (Connection connection = HypersonicEnvironment.createJdbcConnection(dbName);
286 Statement statement = connection.createStatement();
287 ResultSet resultSet =
288 statement.executeQuery("SELECT COUNT(*) FROM " + tableName))
289 {
290 resultSet.next();
291 assertThat(resultSet.getInt(1))
292 .as("Table " + tableName + " must be empty; a successful runTest(...) must"
293 + " have cleaned it up.")
294 .isEqualTo(expectedRows);
295 }
296 }
297
298 private static void closeQuietly(final Connection connection)
299 {
300 try
301 {
302 if (connection != null && !connection.isClosed())
303 {
304 connection.close();
305 }
306 } catch (final SQLException e)
307 {
308
309
310 }
311 }
312
313
314
315
316
317 private static final class ThrowingDatabaseTester extends AbstractDatabaseTester
318 {
319 @Override
320 public IDatabaseConnection getConnection()
321 {
322 throw new AssertionError("This data source's connection must never be opened when"
323 + " it is omitted from the runTest(...) data map.");
324 }
325 }
326 }