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 java.util.ArrayList;
24  import java.util.List;
25  
26  import org.dbunit.database.IDatabaseConnection;
27  import org.dbunit.dataset.CompositeDataSet;
28  import org.dbunit.dataset.DataSetException;
29  import org.dbunit.dataset.IDataSet;
30  import org.dbunit.dataset.ITable;
31  import org.dbunit.dataset.SortedTable;
32  import org.dbunit.dataset.filter.DefaultColumnFilter;
33  import org.dbunit.util.fileloader.DataFileLoader;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  
37  /**
38   * Test case base class supporting prep data and expected data. Prep data is the
39   * data needed for the test to run. Expected data is the data needed to compare
40   * if the test ran successfully.
41   * 
42   * Use this class in two ways:
43   * <ol>
44   * <li>Dependency inject it as its interface into a test class.</li>
45   * <p>
46   * Configure a bean of its interface, injecting a IDatabaseTester and a
47   * DataFileLoader using the databaseTester and a dataFileLoader properties.
48   * </p>
49   * 
50   * <li>Extend it in a test class.</li>
51   * <p>
52   * Obtain IDatabaseTester and DataFileLoader instances (possibly dependency
53   * injecting them into the test class) and set them accordingly, probably in a
54   * setup type of method, such as:
55   * 
56   * <pre>
57   * &#064;Before
58   * public void setDbunitTestDependencies() {
59   *     setDatabaseTester(databaseTester);
60   *     setDataFileLoader(dataFileLoader);
61   * }
62   * </pre>
63   * 
64   * </p>
65   * </ol>
66   * 
67   * To setup, execute, and clean up tests, call the configureTest(), preTest(),
68   * and postTest() methods. Note there is a preTest() convenience method that
69   * takes the same parameters as the configureTest() method; use it instead of
70   * using both configureTest() and preTest().
71   * 
72   * Where the test case calls them depends on data needs:
73   * <ul>
74   * <li>For the whole test case, i.e. in setUp() and tearDown() or &#064;Before
75   * and &#064;After.</li>
76   * <li>In each test method.</li>
77   * <li>Or some combination of both test case setup/teardown and test methods.</li>
78   * </ul>
79   * 
80   * <h4>When each test method requires different prep and expected data</h4>
81   * 
82   * If each test method requires its own prep and expected data, then the test
83   * methods will look something like the following:
84   * 
85   * <pre>
86   * &#064;Autowired
87   * private PrepAndExpectedTestCase tc;
88   * 
89   * &#064;Test
90   * public void testExample() throws Exception {
91   *     String[] prepDataFiles = {}; // define prep files
92   *     String[] expectedDataFiles = {}; // define expected files
93   *     VerifyTableDefinition[] tables = {}; // define tables to verify
94   * 
95   *     tc.preTest(tables, prepDataFiles, expectedDataFiles);
96   * 
97   *     // execute test
98   * 
99   *     tc.postTest();
100  * }
101  * </pre>
102  * 
103  * <h4>When all test methods share the same prep and/or expected data</h4>
104  * 
105  * If each test method can share all of the prep and/or expected data, then use
106  * setUp() for the configureTest() and preTest() calls and tearDown() for the
107  * postTest() call. The methods will look something like the following:
108  * 
109  * <pre>
110  * &#064;Override
111  * protected void setUp() throws Exception {
112  *     setDatabaseTester(databaseTester);
113  *     setDataFileLoader(dataFileLoader);
114  * 
115  *     String[] prepDataFiles = {}; //define prep files
116  *     String[] expectedDataFiles = {}; // define expected files
117  *     VerifyTableDefinition[] tables = {}; //define tables to verify
118  * 
119  *     preTest(tables, prepDataFiles, expectedDataFiles);
120  * 
121  *     // call this if overriding setUp() and databaseTester &amp; dataFileLoader are already set.
122  *     super.setUp();
123  * }
124  * 
125  * &#064;Override
126  * protected void tearDown() throws Exception {
127  *     postTest();
128  *     super.tearDown();
129  * }
130  * 
131  * &#064;Test
132  * public void testExample() throws Exception {
133  *     // execute test
134  * }
135  * </pre>
136  * 
137  * Note that it is unlikely that all test methods can share the same expected
138  * data.
139  * 
140  * <h4>Sharing common (but not all) prep or expected data among test methods.</h4>
141  * 
142  * Put common data in one or more files and pass the needed ones in the correct
143  * data file array.
144  * 
145  * <h4>Notes</h4>
146  * <ol>
147  * <li>For additional examples, refer to the ITs (listed in the See Also
148  * section).</li>
149  * <li>To change the setup or teardown operation (e.g. change the teardown to
150  * org.dbunit.operation.DatabaseOperation.DELETE_ALL), set the setUpOperation or
151  * tearDownOperation property on the databaseTester.</li>
152  * <li>To set DatabaseConfig features/properties, one way is to extend this
153  * class and override the setUpDatabaseConfig(DatabaseConfig config) method from
154  * DatabaseTestCase.</li>
155  * </ol>
156  * 
157  * @see org.dbunit.DefaultPrepAndExpectedTestCaseDiIT
158  * @see org.dbunit.DefaultPrepAndExpectedTestCaseExtIT
159  * 
160  * @author Jeff Jensen jeffjensen AT users.sourceforge.net
161  * @author Last changed by: $Author$
162  * @version $Revision$ $Date$
163  * @since 2.4.8
164  */
165 public class DefaultPrepAndExpectedTestCase extends DBTestCase implements
166 PrepAndExpectedTestCase {
167     private final Logger LOG =
168         LoggerFactory.getLogger(DefaultPrepAndExpectedTestCase.class);
169 
170     private IDatabaseTester databaseTester;
171     private DataFileLoader dataFileLoader;
172 
173     private IDataSet prepDs;
174     private IDataSet expectedDs;
175     private VerifyTableDefinition[] tableDefs;
176 
177     /** Create new instance. */
178     public DefaultPrepAndExpectedTestCase() {
179     }
180 
181     /**
182      * Create new instance with specified dataFileLoader and databasetester.
183      * 
184      * @param dataFileLoader
185      *            Load to use for loading the data files.
186      * @param databaseTester
187      *            Tester to use for database manipulation.
188      */
189     public DefaultPrepAndExpectedTestCase(DataFileLoader dataFileLoader,
190             IDatabaseTester databaseTester) {
191         this.dataFileLoader = dataFileLoader;
192         this.databaseTester = databaseTester;
193     }
194 
195     /**
196      * Create new instance with specified test case name.
197      * 
198      * @param name
199      *            The test case name.
200      */
201     public DefaultPrepAndExpectedTestCase(String name) {
202         super(name);
203     }
204 
205     /**
206      * {@inheritDoc} This implementation returns the databaseTester set by the
207      * test.
208      */
209     public IDatabaseTester newDatabaseTester() throws Exception {
210         // questionable, but there is not a "setter" for any parent...
211         return databaseTester;
212     }
213 
214     /**
215      * {@inheritDoc} Returns the prep dataset.
216      */
217     public IDataSet getDataSet() throws Exception {
218         return prepDs;
219     }
220 
221     /**
222      * {@inheritDoc}
223      */
224     public void configureTest(VerifyTableDefinition[] tables,
225             String[] prepDataFiles, String[] expectedDataFiles)
226     throws Exception {
227         LOG.debug("configureTest: saving instance variables");
228         this.prepDs = makeCompositeDataSet(prepDataFiles);
229         this.expectedDs = makeCompositeDataSet(expectedDataFiles);
230         this.tableDefs = tables;
231     }
232 
233     /**
234      * {@inheritDoc}
235      */
236     public void preTest() throws Exception {
237         setupData();
238     }
239 
240     /**
241      * {@inheritDoc}
242      */
243     public void preTest(VerifyTableDefinition[] tables, String[] prepDataFiles,
244             String[] expectedDataFiles) throws Exception {
245         configureTest(tables, prepDataFiles, expectedDataFiles);
246         preTest();
247     }
248 
249     /**
250      * {@inheritDoc}
251      */
252     public void postTest() throws Exception {
253         postTest(true);
254     }
255 
256     /**
257      * {@inheritDoc}
258      */
259     public void postTest(boolean verifyData) throws Exception {
260         try {
261             if (verifyData) {
262                 verifyData();
263             }
264         } finally {
265             // it is deliberate to have cleanup exceptions shadow verify
266             // failures so user knows db is probably in unknown state (for
267             // those not using an in-memory db or transaction rollback),
268             // otherwise would mask probable cause of subsequent test failures
269             cleanupData();
270         }
271     }
272 
273     /**
274      * {@inheritDoc}
275      */
276     public void cleanupData() throws Exception {
277         IDataSet dataset = new CompositeDataSet(prepDs, expectedDs);
278         String tableNames[] = dataset.getTableNames();
279         int count = tableNames.length;
280         LOG.info("cleanupData: about to clean up {} tables={}", new Integer(
281                 count), tableNames);
282 
283         if (databaseTester == null) {
284             throw new IllegalStateException(
285             "databaseTester is null; must configure or set it first");
286         }
287 
288         databaseTester.setDataSet(dataset);
289         databaseTester.onTearDown();
290         LOG.debug("verifyData: Clean up done");
291     }
292 
293     /**
294      * Use the provided databaseTester to prep the database with the provided
295      * prep dataset. See {@link org.dbunit.IDatabaseTester#onSetup()}.
296      * 
297      * @throws Exception
298      */
299     public void setupData() throws Exception {
300         LOG.debug("setupData: setting prep dataset and inserting rows");
301         if (databaseTester == null) {
302             throw new IllegalStateException(
303             "databaseTester is null; must configure or set it first");
304         }
305 
306         databaseTester.setDataSet(prepDs);
307         databaseTester.onSetup();
308     }
309 
310     /**
311      * For the provided VerifyTableDefinitions, verify each table's actual
312      * results are as expected. Uses the connection from the provided
313      * databaseTester.
314      * 
315      * @throws Exception
316      */
317     public void verifyData() throws Exception {
318         if (databaseTester == null) {
319             throw new IllegalStateException(
320             "databaseTester is null; must configure or set it first");
321         }
322 
323         IDatabaseConnection connection = databaseTester.getConnection();
324 
325         try {
326             int count = tableDefs.length;
327             LOG.info("verifyData: about to verify {} tables={}", new Integer(
328                     count), tableDefs);
329             if (count == 0) {
330                 LOG.warn("No tables to verify;"
331                         + " no VerifyTableDefinitions specified");
332             }
333 
334             for (int i = 0; i < count; i++) {
335                 VerifyTableDefinition td = tableDefs[i];
336                 String[] excludeColumns = td.getColumnExclusionFilters();
337                 String[] includeColumns = td.getColumnInclusionFilters();
338                 String tableName = td.getTableName();
339 
340                 LOG.info("Verifying table '{}'", tableName);
341 
342                 LOG.debug("  Loading its rows from expected dataset");
343                 ITable expectedTable = null;
344                 try {
345                     expectedTable = expectedDs.getTable(tableName);
346                 } catch (DataSetException e) {
347                     final String msg =
348                         "Problem obtaining table '" + tableName
349                         + "' from expected dataset";
350                     LOG.error(msg, e);
351                     throw new DataSetException(msg, e);
352                 }
353 
354                 LOG.debug("  Loading its rows from actual table");
355                 ITable actualTable = null;
356                 try {
357                     actualTable = connection.createTable(tableName);
358                 } catch (DataSetException e) {
359                     final String msg =
360                         "Problem obtaining table '" + tableName
361                         + "' from actual dataset";
362                     LOG.error(msg, e);
363                     throw new DataSetException(msg, e);
364                 }
365 
366                 verifyData(expectedTable, actualTable, excludeColumns,
367                         includeColumns);
368             }
369         } finally {
370             LOG.debug("verifyData: Verification done, closing connection");
371             connection.close();
372         }
373     }
374 
375     /**
376      * For the specified expected and actual tables (and excluding and including
377      * the specified columns), verify the actual data is as expected.
378      * 
379      * @param expectedTable
380      *            The expected table to compare the actual table to.
381      * @param actualTable
382      *            The actual table to compare to the expected table.
383      * @param excludeColumns
384      *            The column names to exclude from comparison. See
385      *            {@link org.dbunit.dataset.filter.DefaultColumnFilter#excludeColumn(String)}
386      *            .
387      * @param includeColumns
388      *            The column names to only include in comparison. See
389      *            {@link org.dbunit.dataset.filter.DefaultColumnFilter#includeColumn(String)}
390      *            .
391      * @throws DatabaseUnitException
392      */
393     public void verifyData(ITable expectedTable, ITable actualTable,
394             String[] excludeColumns, String[] includeColumns)
395     throws DatabaseUnitException {
396         // Filter out the columns from the expected and actual results
397         LOG.debug("Applying filters to expected table");
398         ITable expectedFilteredTable =
399             applyColumnFilters(expectedTable, excludeColumns,
400                     includeColumns);
401         LOG.debug("Applying filters to actual table");
402         ITable actualFilteredTable =
403             applyColumnFilters(actualTable, excludeColumns, includeColumns);
404 
405         LOG.debug("Sorting expected table");
406         SortedTable expectedSortedTable =
407             new SortedTable(expectedFilteredTable);
408         LOG.debug("Sorting actual table");
409         SortedTable actualSortedTable =
410             new SortedTable(actualFilteredTable, expectedFilteredTable
411                     .getTableMetaData());
412 
413         LOG.debug("Comparing expected table to actual table");
414         Assertion.assertEquals(expectedSortedTable, actualSortedTable);
415     }
416 
417     /**
418      * Make a <code>IDataSet</code> from the specified files.
419      * 
420      * @param dataFiles
421      *            Represents the array of dbUnit data files.
422      * @return The composite dataset.
423      * @throws DataSetException
424      *             On dbUnit errors.
425      */
426     public IDataSet makeCompositeDataSet(String[] dataFiles)
427     throws DataSetException {
428         if (dataFileLoader == null) {
429             throw new IllegalStateException(
430             "dataFileLoader is null; must configure or set it first");
431         }
432 
433         int count = dataFiles.length;
434         LOG.debug("makeCompositeDataSet: dataFiles count=" + count);
435         if (count == 0) {
436             LOG.info("makeCompositeDataSet: Specified zero data files");
437         }
438 
439         List list = new ArrayList();
440         for (int i = 0; i < count; i++) {
441             IDataSet ds = dataFileLoader.load(dataFiles[i]);
442             list.add(ds);
443         }
444 
445         IDataSet[] dataSet = (IDataSet[]) list.toArray(new IDataSet[] {});
446         IDataSet compositeDS = new CompositeDataSet(dataSet);
447         return compositeDS;
448     }
449 
450     /**
451      * Apply the specified exclude and include column filters to the specified
452      * table.
453      * 
454      * @param table
455      *            The table to apply the filters to.
456      * @param excludeColumns
457      *            The exclude filters; use null or empty array to mean exclude
458      *            none.
459      * @param includeColumns
460      *            The include filters; use null to mean include all.
461      * @return The filtered table.
462      * @throws DataSetException
463      */
464     public ITable applyColumnFilters(ITable table, String[] excludeColumns,
465             String[] includeColumns) throws DataSetException {
466         ITable filteredTable = table;
467 
468         if (table == null) {
469             throw new IllegalArgumentException("table is null");
470         }
471 
472         // note: dbunit interprets an empty inclusion filter array as one
473         // not wanting to compare anything!
474         if (includeColumns == null) {
475             LOG.debug("applyColumnFilters: including columns=(all)");
476         } else {
477             LOG.debug("applyColumnFilters: including columns='{}'",
478                     new Object[] {includeColumns});
479             filteredTable =
480                 DefaultColumnFilter.includedColumnsTable(filteredTable,
481                         includeColumns);
482         }
483 
484         if (excludeColumns == null || excludeColumns.length == 0) {
485             LOG.debug("applyColumnFilters: excluding columns=(none)");
486         } else {
487             LOG.debug("applyColumnFilters: excluding columns='{}'",
488                     new Object[] {excludeColumns});
489             filteredTable =
490                 DefaultColumnFilter.excludedColumnsTable(filteredTable,
491                         excludeColumns);
492         }
493 
494         return filteredTable;
495     }
496 
497     /**
498      * {@inheritDoc}
499      */
500     public IDataSet getPrepDataset() {
501         return prepDs;
502     }
503 
504     /**
505      * {@inheritDoc}
506      */
507     public IDataSet getExpectedDataset() {
508         return expectedDs;
509     }
510 
511     /**
512      * Get the databaseTester.
513      * 
514      * @see {@link databaseTester}.
515      * 
516      * @return The databaseTester.
517      */
518     public IDatabaseTester getDatabaseTester() {
519         return databaseTester;
520     }
521 
522     /**
523      * Set the databaseTester.
524      * 
525      * @see {@link databaseTester}.
526      * 
527      * @param databaseTester
528      *            The databaseTester to set.
529      */
530     public void setDatabaseTester(IDatabaseTester databaseTester) {
531         this.databaseTester = databaseTester;
532     }
533 
534     /**
535      * Get the dataFileLoader.
536      * 
537      * @see {@link dataFileLoader}.
538      * 
539      * @return The dataFileLoader.
540      */
541     public DataFileLoader getDataFileLoader() {
542         return dataFileLoader;
543     }
544 
545     /**
546      * Set the dataFileLoader.
547      * 
548      * @see {@link dataFileLoader}.
549      * 
550      * @param dataFileLoader
551      *            The dataFileLoader to set.
552      */
553     public void setDataFileLoader(DataFileLoader dataFileLoader) {
554         this.dataFileLoader = dataFileLoader;
555     }
556 
557     /**
558      * Set the prepDs.
559      * 
560      * @see {@link prepDs}.
561      * 
562      * @param prepDs
563      *            The prepDs to set.
564      */
565     public void setPrepDs(IDataSet prepDs) {
566         this.prepDs = prepDs;
567     }
568 
569     /**
570      * Set the expectedDs.
571      * 
572      * @see {@link expectedDs}.
573      * 
574      * @param expectedDs
575      *            The expectedDs to set.
576      */
577     public void setExpectedDs(IDataSet expectedDs) {
578         this.expectedDs = expectedDs;
579     }
580 
581     /**
582      * Get the tableDefs.
583      * 
584      * @see {@link tableDefs}.
585      * 
586      * @return The tableDefs.
587      */
588     public VerifyTableDefinition[] getTableDefs() {
589         return tableDefs;
590     }
591 
592     /**
593      * Set the tableDefs.
594      * 
595      * @see {@link tableDefs}.
596      * 
597      * @param tableDefs
598      *            The tableDefs to set.
599      */
600     public void setTableDefs(VerifyTableDefinition[] tableDefs) {
601         this.tableDefs = tableDefs;
602     }
603 }