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