[seam-commits] Seam SVN: r11275 - in branches/enterprise/JBPAPP_5_0: examples/quartz/src/org/jboss/seam/example/quartz/test and 2 other directories.

seam-commits at lists.jboss.org seam-commits at lists.jboss.org
Mon Jul 13 07:07:19 EDT 2009


Author: manaRH
Date: 2009-07-13 07:07:19 -0400 (Mon, 13 Jul 2009)
New Revision: 11275

Modified:
   branches/enterprise/JBPAPP_5_0/doc/Seam_Reference_Guide/en-US/Testing.xml
   branches/enterprise/JBPAPP_5_0/examples/quartz/src/org/jboss/seam/example/quartz/test/testng.xml
   branches/enterprise/JBPAPP_5_0/examples/seamdiscs/src/org/jboss/seam/example/seamdiscs/test/testng.xml
   branches/enterprise/JBPAPP_5_0/src/main/org/jboss/seam/mock/AbstractDBUnitSeamTest.java
   branches/enterprise/JBPAPP_5_0/src/main/org/jboss/seam/mock/DBUnitSeamTest.java
Log:
back ported JBSEAM-4289

Modified: branches/enterprise/JBPAPP_5_0/doc/Seam_Reference_Guide/en-US/Testing.xml
===================================================================
--- branches/enterprise/JBPAPP_5_0/doc/Seam_Reference_Guide/en-US/Testing.xml	2009-07-13 10:33:11 UTC (rev 11274)
+++ branches/enterprise/JBPAPP_5_0/doc/Seam_Reference_Guide/en-US/Testing.xml	2009-07-13 11:07:19 UTC (rev 11275)
@@ -536,13 +536,13 @@
           <title>Integration Testing with Mock Data</title>
           
           <para>
-            If you need to insert or clean data in your database before each 
+            If you want to insert or clean data in your database before each
             test you can use Seam's integration with DBUnit.  To do this, extend 
-            <literal>DBUnitSeamTest</literal> rather than SeamTest.
+            <literal>DBUnitSeamTest</literal> rather than <literal>SeamTest</literal>.
          </para>
          
          <para>
-            You need to provide a dataset for DBUnit.  
+            You have to provide a dataset for DBUnit.
          </para>
          
          <caution>
@@ -566,7 +566,8 @@
 </dataset>]]></programlisting>
 
          <para>
-            and tell Seam about it by overriding <literal>prepareDBUnitOperations()</literal>:
+            In your test class, configure your dataset with overriding
+            <literal>prepareDBUnitOperations()</literal>:
          </para>
       
          <programlisting role="JAVA"><![CDATA[protected void prepareDBUnitOperations() {
@@ -593,36 +594,31 @@
            setting a TestNG test parameter named <literal>datasourceJndiName</literal>:
          </para>
          
-         <programlisting role="XML">
-   <![CDATA[<parameter name="datasourceJndiName" value="java:/seamdiscsDatasource"/>]]>
-         </programlisting> 
+         <programlisting role="XML"><![CDATA[<parameter name="datasourceJndiName" value="java:/seamdiscsDatasource"/>]]></programlisting>
          
          <para>
             DBUnitSeamTest has support for MySQL and HSQL - you need to tell it
-            which database is being used:
+            which database is being used, otherwise it defaults to HSQL:
          </para>
          
-         <programlisting><![CDATA[<parameter name="database" value="HSQL" />]]></programlisting>
+          <programlisting role="XML"><![CDATA[<parameter name="database" value="MYSQL" />]]></programlisting>
          
          <para>
             It also allows you to insert binary data into the test data set (n.b.
             this is untested on Windows). You need to tell it where to locate
-            these resources:
+            these resources on your classpath:
          </para>
          
-         <programlisting><![CDATA[<parameter name="binaryDir" value="images/" />]]></programlisting> 
+          <programlisting role="XML"><![CDATA[<parameter name="binaryDir" value="images/" />]]></programlisting> 
           
          <para>
-            You <emphasis>must</emphasis> specify these three parameters in your
-            <literal>testng.xml</literal>.
+            You do not have to configure any of these parameters if you use HSQL and have
+            no binary imports. However, unless you specify <literal>datasourceJndiName</literal>
+            in your test configuration, you will have to call <literal>setDatabaseJndiName()</literal>
+            before your test runs. If you are not using HSQL or MySQL, you need to override some
+            methods. See the Javadoc of <literal>DBUnitSeamTest</literal> for more details.
          </para>
          
-         <para>
-            If you want to use DBUnitSeamTest with another database, you'll need
-            to implement some methods. Read the javadoc of 
-            <literal>AbstractDBUnitSeamTest</literal> for more.
-         </para> 
-          
         </section>
         
         <section id="testing.mail">

Modified: branches/enterprise/JBPAPP_5_0/examples/quartz/src/org/jboss/seam/example/quartz/test/testng.xml
===================================================================
--- branches/enterprise/JBPAPP_5_0/examples/quartz/src/org/jboss/seam/example/quartz/test/testng.xml	2009-07-13 10:33:11 UTC (rev 11274)
+++ branches/enterprise/JBPAPP_5_0/examples/quartz/src/org/jboss/seam/example/quartz/test/testng.xml	2009-07-13 11:07:19 UTC (rev 11275)
@@ -5,7 +5,6 @@
       
         <parameter name="datasourceJndiName" value="java:/DefaultDS"/>
         <parameter name="database" value="HSQL" />
-        <parameter name="binaryDir" value="" />
         
 		<classes>
 			<class name="org.jboss.seam.example.quartz.test.AccountTest" />

Modified: branches/enterprise/JBPAPP_5_0/examples/seamdiscs/src/org/jboss/seam/example/seamdiscs/test/testng.xml
===================================================================
--- branches/enterprise/JBPAPP_5_0/examples/seamdiscs/src/org/jboss/seam/example/seamdiscs/test/testng.xml	2009-07-13 10:33:11 UTC (rev 11274)
+++ branches/enterprise/JBPAPP_5_0/examples/seamdiscs/src/org/jboss/seam/example/seamdiscs/test/testng.xml	2009-07-13 11:07:19 UTC (rev 11275)
@@ -4,7 +4,6 @@
       
         <parameter name="datasourceJndiName" value="java:/seamdiscsDatasource"/>
         <parameter name="database" value="HSQL" />
-        <parameter name="binaryDir" value="" />
     
         <classes>
             <class name="org.jboss.seam.example.seamdiscs.test.DisplayArtistTest" />

Modified: branches/enterprise/JBPAPP_5_0/src/main/org/jboss/seam/mock/AbstractDBUnitSeamTest.java
===================================================================
--- branches/enterprise/JBPAPP_5_0/src/main/org/jboss/seam/mock/AbstractDBUnitSeamTest.java	2009-07-13 10:33:11 UTC (rev 11274)
+++ branches/enterprise/JBPAPP_5_0/src/main/org/jboss/seam/mock/AbstractDBUnitSeamTest.java	2009-07-13 11:07:19 UTC (rev 11275)
@@ -1,461 +0,0 @@
-/*
- * JBoss, Home of Professional Open Source
- *
- * Distributable under LGPL license.
- * See terms of license at gnu.org.
- */
-package org.jboss.seam.mock;
-
-import static org.jboss.seam.mock.AbstractDBUnitSeamTest.Database.HSQL;
-import static org.jboss.seam.mock.AbstractDBUnitSeamTest.Database.MYSQL;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.sql.Connection;
-import java.sql.Types;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.sql.DataSource;
-
-import org.dbunit.database.DatabaseConfig;
-import org.dbunit.database.DatabaseConnection;
-import org.dbunit.database.IDatabaseConnection;
-import org.dbunit.dataset.IDataSet;
-import org.dbunit.dataset.ReplacementDataSet;
-import org.dbunit.dataset.datatype.DataType;
-import org.dbunit.dataset.datatype.DataTypeException;
-import org.dbunit.dataset.datatype.DefaultDataTypeFactory;
-import org.dbunit.dataset.xml.FlatXmlDataSet;
-import org.dbunit.operation.DatabaseOperation;
-import org.jboss.seam.log.LogProvider;
-import org.jboss.seam.log.Logging;
-
-/**
- * Utility for integration testing with Seam and DBUnit datasets.
- * <p>
- * Subclass this class instead of <tt>SeamTest</tt> if you need to insert or clean data in
- * your database before and after a test. You need to implement <tt>prepareDBUnitOperations()</tt> and
- * add instances of <tt>DataSetOperation</tt>s to the * <tt>beforeTestOperations</tt> and
- * <tt>afterTestOperations</tt> lists. An example:
- * <pre>
- * public class MyTest extends DBUnitSeamTest {
- *
- *   protected void prepareDBUnitOperations() {
- *       beforeTestOperations.add(
- *          new DataSetOperation("my/datasets/BaseData.xml")
- *       );
- *       beforeTestOperations.add(
- *           new DataSetOperation("my/datasets/AdditionalData.xml", DatabaseOperation.INSERT)
- *       );
- *   }
- * ... // Various test methods with @Test annotation
- * }
- * </pre>
- * <p>
- * Note that <tt>DataSetOperation</tt> defaults to <tt>DatabaseOperation.CLEAN_INSERT</tt> if no
- * other operation is specified as a constructor argument. The above example cleans all tables defined
- * in <tt>BaseData.xml</tt>, then inserts all rows declared in <tt>BaseData.xml</tt>, then inserts
- * all the rows declared in <tt>AdditionalData.xml</tt>. This executes before every each test method
- * is invoked. If you require extra cleanup after a test method executes, add operations to the
- * <tt>afterTestOperations</tt> list.
- * </p>
- * <p>
- * A test class obtains the database connection for loading and cleaning of datasets in one of the following ways:
- * </p>
- * <dl>
- * <li>A TestNG test parameter named <tt>datasourceJndiName</tt> is provided by the TestNG test runner, which
- * automatically calls <tt>setDatasourceJndiName()</tt> on the test class before a logical test runs.</li>
- * 
- * <li>An instance of a test class is created manually and the <tt>datasourceJndiName</tt> is passed as a
- * constructor argument.</li>
- *
- * <li>An instance of a test class is created manually and the <tt>setDatasourceJndiName()</tt> method is
- * called after creation and before a test runs.</li>
- *
- * <li>A subclass overrides the <tt>getConnection()</tt> method and returns a custom database connection.</li>
- *
- * </dl>
- * <p>
- * Referential integrity checks (foreign keys) will be or have to be disabled on the database connection
- * used for DBUnit operations. This makes adding circular references in datasets easier. Referential integrity checks
- * are enabled again after the connection has been used.
- * </p>
- * <p>
- * <b>Note that the methods <tt>disableReferentialIntegrity()</tt>,
- * <tt>enableReferentialIntegrity()</tt>, and <tt>editConfig()</tt> are implemented for HSQL DB. If you want to run 
- * unit tests on any other DBMS, you need to override these methods and implement them for your DBMS.</b>
- * </p>
- *
- * @author Christian Bauer
- */
-public abstract class AbstractDBUnitSeamTest extends AbstractSeamTest 
-{
-
-    public enum Database 
-    {
-        HSQL, MYSQL
-    }
-
-    private LogProvider log = Logging.getLogProvider(DBUnitSeamTest.class);
-    
-    private String datasourceJndiName;
-    private String binaryDir;
-    private Database database = HSQL;
-    protected List<DataSetOperation> beforeTestOperations = new ArrayList<DataSetOperation>();
-    protected List<DataSetOperation> afterTestOperations = new ArrayList<DataSetOperation>();
-
-    protected AbstractDBUnitSeamTest() 
-    {
-    }
-
-    protected AbstractDBUnitSeamTest(String datasourceJndiName) 
-    {
-        this.datasourceJndiName = datasourceJndiName;
-    }
-
-    public void setDatasourceJndiName(String datasourceJndiName) 
-    {
-        this.datasourceJndiName = datasourceJndiName;
-    }
-
-    public void setBinaryDir(String binaryDir) 
-    {
-        this.binaryDir = binaryDir;
-    }
-
-    public void setDatabase(String database) 
-    {
-       if (database != null)
-       {
-          this.database = Database.valueOf(database.toUpperCase());
-       }
-    }
-
-    @Override
-    public void setupClass() throws Exception 
-    {
-        super.setupClass();
-        prepareDBUnitOperations();
-    }
-
-    @Override
-    public void begin() 
-    {
-        super.begin();
-        executeOperations(beforeTestOperations);
-    }
-    
-    @Override
-    public void end() 
-    {
-        super.end();
-        executeOperations(afterTestOperations);
-    }
-
-    private void executeOperations(List<DataSetOperation> list) 
-    {
-        IDatabaseConnection con = null;
-        try 
-        {
-            con = getConnection();
-            disableReferentialIntegrity(con);
-            for (DataSetOperation op : list) 
-            {
-                op.execute(con);
-            }
-            enableReferentialIntegrity(con);
-        } 
-        finally 
-        {
-            if (con != null) 
-            {
-                try 
-                {
-                    con.close();
-                }
-                catch (Exception ex) 
-                {
-                    ex.printStackTrace(System.err);
-                }
-            }
-        }
-    }
-
-    protected class DataSetOperation 
-    {
-        String dataSetLocation;
-        ReplacementDataSet dataSet;
-        DatabaseOperation operation;
-
-        /**
-         * Defaults to <tt>DatabaseOperation.CLEAN_INSERT</tt>
-         * @param dataSetLocation location of DBUnit dataset
-         */
-        public DataSetOperation(String dataSetLocation)
-        {
-            this(dataSetLocation, DatabaseOperation.CLEAN_INSERT);
-        }
-        
-        /**
-         * Defaults to <tt>DatabaseOperation.CLEAN_INSERT</tt>
-         */
-        public DataSetOperation(String dataSetLocation, String dtdLocation)
-        {
-            this(dataSetLocation, dtdLocation, DatabaseOperation.CLEAN_INSERT);
-        }
-
-        public DataSetOperation(String dataSetLocation, String dtdLocation, DatabaseOperation operation) 
-        {
-           log.debug(">>> Preparing dataset: " + dataSetLocation + " <<<");
-
-           // Load the base dataset file
-           InputStream input = Thread.currentThread().getContextClassLoader().getResourceAsStream(dataSetLocation);
-           try 
-           {
-               InputStream dtdInput = null; 
-               if (dtdLocation != null)
-               {
-                   dtdInput = Thread.currentThread().getContextClassLoader().getResourceAsStream(dtdLocation);
-               }
-               if (dtdInput == null)
-               {
-                   this.dataSet = new ReplacementDataSet( new FlatXmlDataSet(input) );
-               }
-               else
-               {
-                   this.dataSet = new ReplacementDataSet( new FlatXmlDataSet(input, dtdInput) );
-               }
-           }
-           catch (Exception ex) 
-           {
-               throw new RuntimeException(ex);
-           }
-           this.dataSet.addReplacementObject("[NULL]", null);
-           if (binaryDir != null) 
-           {
-               this.dataSet.addReplacementSubstring("[BINARY_DIR]", getBinaryDirFullpath().toString());
-           }
-           this.operation = operation;
-           this.dataSetLocation = dataSetLocation;
-        }
-
-        
-
-        public DataSetOperation(String dataSetLocation, DatabaseOperation operation) 
-        {
-           this(dataSetLocation, null, operation);
-        }
-
-        public IDataSet getDataSet() 
-        {
-            return dataSet;
-        }
-
-        public DatabaseOperation getOperation() 
-        {
-            return operation;
-        }
-
-        public void execute(IDatabaseConnection connection) 
-        {
-            try 
-            {
-                this.operation.execute(connection, dataSet);
-            }
-            catch (Exception ex) 
-            {
-                throw new RuntimeException(ex);
-            }
-        }
-
-        @Override
-        public String toString() 
-        {
-            // TODO: This is not pretty because DBUnit's DatabaseOperation doesn't implement toString() properly
-            return operation.getClass() + " with dataset: " + dataSetLocation;
-        }
-    }
-
-    // Subclasses can/have to override the following methods
-
-    /**
-     * Override this method if you want to provide your own DBUnit <tt>IDatabaseConnection</tt> instance.
-     * <p/>
-     * If you do not override this, default behavior is to use the * configured datasource name and
-     * to obtain a connection with a JNDI lookup.
-     *
-     * @return a DBUnit database connection (wrapped)
-     */
-    protected IDatabaseConnection getConnection() 
-    {
-        try 
-        {
-            DataSource datasource = ((DataSource)getInitialContext().lookup(datasourceJndiName));
-
-            // Get a JDBC connection from JNDI datasource
-            Connection con = datasource.getConnection();
-            IDatabaseConnection dbUnitCon = new DatabaseConnection(con);
-            editConfig(dbUnitCon.getConfig());
-            return dbUnitCon;
-        }
-        catch (Exception ex) 
-        {
-            throw new RuntimeException(ex);
-        }
-    }
-
-    /**
-     * Override this method if you aren't using HSQL DB.
-     * <p/>
-     * Execute whatever statement is necessary to either defer or disable foreign
-     * key constraint checking on the given database connection, which is used by
-     * DBUnit to import datasets.
-     *
-     * @param con A DBUnit connection wrapper, which is used afterwards for dataset operations
-     */
-    protected void disableReferentialIntegrity(IDatabaseConnection con) 
-    {
-        try 
-        {
-            if (database.equals(HSQL)) 
-            {
-                con.getConnection().prepareStatement("set referential_integrity FALSE").execute(); // HSQL DB
-            }
-            else if (database.equals(MYSQL)) 
-            {
-                con.getConnection().prepareStatement("set foreign_key_checks=0").execute(); // MySQL > 4.1.1
-            }
-        }
-        catch (Exception ex) 
-        {
-            throw new RuntimeException(ex);
-        }
-    }
-
-    /**
-     * Override this method if you aren't using HSQL DB.
-     * <p/>
-     * Execute whatever statement is necessary to enable integrity constraint checks after
-     * dataset operations.
-     *
-     * @param con A DBUnit connection wrapper, before it is used by the application again
-     */
-    protected void enableReferentialIntegrity(IDatabaseConnection con) {
-        try {
-            if (database.equals(HSQL)) 
-            {
-                con.getConnection().prepareStatement("set referential_integrity TRUE").execute();  // HSQL DB
-            }
-            else if (database.equals(MYSQL)) 
-            {
-                con.getConnection().prepareStatement("set foreign_key_checks=1").execute(); // MySQL > 4.1.1
-            }
-        }
-        catch (Exception ex) 
-        {
-            throw new RuntimeException(ex);
-        }
-    }
-
-    /**
-     * Override this method if you require DBUnit configuration features or additional properties.
-     * <p>
-     * Called after a connection has been obtaind and before the connection is used. Can be a
-     * NOOP method if no additional settings are necessary for your DBUnit/DBMS setup.
-     *
-     * @param config A DBUnit <tt>DatabaseConfig</tt> object for setting properties and features
-     */
-    protected void editConfig(DatabaseConfig config) 
-    {
-        if (database.equals(HSQL)) {
-            // DBUnit/HSQL bugfix
-            // http://www.carbonfive.com/community/archives/2005/07/dbunit_hsql_and.html
-            config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new DefaultDataTypeFactory() 
-            {
-               @Override
-                public DataType createDataType(int sqlType, String sqlTypeName)
-                  throws DataTypeException 
-                  {
-                   if (sqlType == Types.BOOLEAN) 
-                   {
-                      return DataType.BOOLEAN;
-                    }
-                   return super.createDataType(sqlType, sqlTypeName);
-                 }
-            });
-        }
-    }
-
-    /**
-     * Resolves the binary dir location with the help of the classloader, we need the
-     * absolute full path of that directory.
-     *
-     * @return URL full absolute path of the binary directory
-     */
-    protected URL getBinaryDirFullpath() 
-    {
-        if (binaryDir == null) {
-            throw new RuntimeException("Please set binaryDir property to location of binary test files");
-        }
-        return getResourceURL(binaryDir);
-    }
-
-    protected URL getResourceURL(String resource) 
-    {
-        URL url = Thread.currentThread().getContextClassLoader().getResource(resource);
-        if (url == null) 
-        {
-            throw new RuntimeException("Could not find resource with classloader: " + resource);
-        }
-        return url;
-    }
-
-    protected byte[] getBinaryFile(String filename) throws Exception 
-    {
-        File file = new File(getResourceURL(binaryDir + "/" + filename).toURI());
-        InputStream is = new FileInputStream(file);
-
-        // Get the size of the file
-        long length = file.length();
-
-        if (length > Integer.MAX_VALUE) 
-        {
-            // File is too large
-        }
-
-        // Create the byte array to hold the data
-        byte[] bytes = new byte[(int)length];
-
-        // Read in the bytes
-        int offset = 0;
-        int numRead;
-        while (offset < bytes.length
-               && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) 
-        {
-            offset += numRead;
-        }
-
-        // Ensure all the bytes have been read in
-        if (offset < bytes.length) 
-        {
-            throw new IOException("Could not completely read file "+file.getName());
-        }
-    
-        // Close the input stream and return bytes
-        is.close();
-        return bytes;
-    }
-
-    /**
-     * Implement this in a subclass.
-     * <p>
-     * Use it to stack DBUnit <tt>DataSetOperation</tt>'s with
-     * the <tt>beforeTestOperations</tt> and <tt>afterTestOperations</tt> lists.
-     */
-    protected abstract void prepareDBUnitOperations();
-
-}

Modified: branches/enterprise/JBPAPP_5_0/src/main/org/jboss/seam/mock/DBUnitSeamTest.java
===================================================================
--- branches/enterprise/JBPAPP_5_0/src/main/org/jboss/seam/mock/DBUnitSeamTest.java	2009-07-13 10:33:11 UTC (rev 11274)
+++ branches/enterprise/JBPAPP_5_0/src/main/org/jboss/seam/mock/DBUnitSeamTest.java	2009-07-13 11:07:19 UTC (rev 11275)
@@ -1,81 +1,476 @@
 package org.jboss.seam.mock;
 
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.AfterSuite;
+import static org.jboss.seam.mock.DBUnitSeamTest.Database.HSQL;
+import static org.jboss.seam.mock.DBUnitSeamTest.Database.MYSQL;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.sql.Connection;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.dbunit.database.DatabaseConfig;
+import org.dbunit.database.DatabaseConnection;
+import org.dbunit.database.IDatabaseConnection;
+import org.dbunit.dataset.IDataSet;
+import org.dbunit.dataset.ReplacementDataSet;
+import org.dbunit.dataset.datatype.DataType;
+import org.dbunit.dataset.datatype.DataTypeException;
+import org.dbunit.dataset.datatype.DefaultDataTypeFactory;
+import org.dbunit.dataset.xml.FlatXmlDataSet;
+import org.dbunit.operation.DatabaseOperation;
+import org.jboss.seam.log.LogProvider;
+import org.jboss.seam.log.Logging;
 import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Optional;
 import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.BeforeSuite;
-import org.testng.annotations.Parameters;
+import org.testng.annotations.AfterMethod;
 
-public abstract class DBUnitSeamTest extends AbstractDBUnitSeamTest
+/**
+ * Utility for integration testing with Seam and DBUnit datasets.
+ * <p>
+ * Subclass this class instead of <tt>SeamTest</tt> if you need to insert or clean data in
+ * your database before and after a test. You need to implement <tt>prepareDBUnitOperations()</tt> and
+ * add instances of <tt>DataSetOperation</tt>s to the <tt>beforeTestOperations</tt> and
+ * <tt>afterTestOperations</tt> lists. An example:
+ * <pre>
+ * public class MyTest extends DBUnitSeamTest {
+ *
+ *   protected void prepareDBUnitOperations() {
+ *       beforeTestOperations.add(
+ *          new DataSetOperation("my/datasets/BaseData.xml")
+ *       );
+ *       beforeTestOperations.add(
+ *           new DataSetOperation("my/datasets/AdditionalData.xml", DatabaseOperation.INSERT)
+ *       );
+ *   }
+ * ... // Various test methods with @Test annotation
+ * }
+ * </pre>
+ * <p>
+ * Note that <tt>DataSetOperation</tt> defaults to <tt>DatabaseOperation.CLEAN_INSERT</tt> if no
+ * other operation is specified as a constructor argument. The above example cleans all tables defined
+ * in <tt>BaseData.xml</tt>, then inserts all rows declared in <tt>BaseData.xml</tt>, then inserts
+ * all the rows declared in <tt>AdditionalData.xml</tt>. This executes before each test method
+ * is invoked. If you require extra cleanup after a test method executes, add operations to the
+ * <tt>afterTestOperations</tt> list.
+ * </p>
+ * <p>
+ * A test class obtains the database connection for loading and cleaning of datasets in one of the following ways:
+ * </p>
+ * <dl>
+ * <li>A TestNG test parameter named <tt>datasourceJndiName</tt> is provided by the TestNG test runner, which
+ * automatically calls <tt>setDatasourceJndiName()</tt> on the test class before a logical test runs.</li>
+ * <p/>
+ * <li>An instance of a test class is created manually and the <tt>setDatasourceJndiName()</tt> method is
+ * called after creation and before a test runs.</li>
+ * <p/>
+ * <li>A subclass overrides the <tt>getConnection()</tt> method and returns a custom database connection.</li>
+ * <p/>
+ * </dl>
+ * <p>
+ * Binary files can be imported into the database from a binary directory, configured with the TestNG parameter
+ * <tt>binaryDir</tt> or by calling <tt>setBinaryDir()</tt> before a test runs. The binary directory is a classpath
+ * reference, e.g. <tt>my/org/test/package/binarydir</tt>. In your DBUnit XML flat dataset, declare the path of your file
+ * as follows: <tt>&lt;MYTABLE MYCOLUMN="[BINARY_DIR]/mytestfile.png"/&gt;</tt>
+ * </p>
+ * <p>
+ * Referential integrity checks (foreign keys) will be or have to be disabled on the database connection
+ * used for DBUnit operations. This makes adding circular references in datasets easier (especially for nullable
+ * foreign key columns). Referential integrity checks are enabled again after the connection has been used.
+ * </p>
+ * <p>
+ * <b>IMPORTANT: The methods <tt>disableReferentialIntegrity()</tt>,
+ * <tt>enableReferentialIntegrity()</tt>, and <tt>editConfig()</tt> are implemented for HSQL and MySQL. You need to
+ * configure the DBMS you are using with the <tt>database</tt> TestNG parameter or by calling <tt>setDatabase()</tt>
+ * before the the test run. If you want to run unit tests on any other DBMS, you need to override the
+ * <tt>disableReferentialIntegrity()</tt> and <tt>enableReferentialIntegrity()</tt> methods and implement them
+ * for your DBMS. Also note that by default, if no <tt>database</tt> TestNG parameter has been set or if the
+ * <tt>setDatabase()</tt> method has not been called before test runs, HSQL DB will be used as the default.</b>
+ * </p>
+ * @author Christian Bauer
+ */
+public abstract class DBUnitSeamTest extends SeamTest
 {
 
-   @Override
+   public enum Database
+   {
+      HSQL, MYSQL
+   }
+
+   private LogProvider log = Logging.getLogProvider(DBUnitSeamTest.class);
+
+   protected String datasourceJndiName;
+   protected String binaryDir;
+   protected Database database = HSQL;
+   protected List<DataSetOperation> beforeTestOperations = new ArrayList<DataSetOperation>();
+   protected List<DataSetOperation> afterTestOperations = new ArrayList<DataSetOperation>();
+
    @BeforeClass
    @Parameters("datasourceJndiName")
-   public void setDatasourceJndiName(String datasourceJndiName) 
+   public void setDatasourceJndiName(@Optional String datasourceJndiName)
    {
-      super.setDatasourceJndiName(datasourceJndiName);
+      this.datasourceJndiName = datasourceJndiName;
    }
 
-   @Override
    @BeforeClass
    @Parameters("binaryDir")
-   public void setBinaryDir(String binaryDir) 
+   public void setBinaryDir(@Optional String binaryDir)
    {
-       super.setBinaryDir(binaryDir);
+      this.binaryDir = binaryDir;
    }
 
-   @Override
    @BeforeClass
    @Parameters("database")
-   public void setDatabase(String database) 
+   public void setDatabase(@Optional String database)
    {
-       super.setDatabase(database);
+      if (database != null)
+      {
+         this.database = Database.valueOf(database.toUpperCase());
+      }
    }
 
+   @BeforeClass
    @Override
-   @BeforeClass
-   public void setupClass() throws Exception 
+   public void setupClass() throws Exception
    {
-       super.setupClass();
+      super.setupClass();
+      prepareDBUnitOperations();
    }
-   
-   @Override
-   @AfterClass
-   protected void cleanupClass() throws Exception
+
+   @BeforeMethod
+   public void prepareDataBeforeTest()
    {
-       super.cleanupClass();
+      executeOperations(beforeTestOperations);
    }
-   
-   @Override
-   @BeforeSuite
-   protected void startSeam() throws Exception
+
+   @AfterMethod
+   public void cleanDataAfterTest()
    {
-       super.startSeam();
+      executeOperations(afterTestOperations);
    }
-   
-   @Override
-   @AfterSuite
-   protected void stopSeam() throws Exception
+
+   private void executeOperations(List<DataSetOperation> list)
    {
-       super.stopSeam();
-   } 
-   
+      IDatabaseConnection con = null;
+      try
+      {
+         con = getConnection();
+         disableReferentialIntegrity(con);
+         for (DataSetOperation op : list)
+         {
+            op.execute(con);
+         }
+         enableReferentialIntegrity(con);
+      }
+      finally
+      {
+         if (con != null)
+         {
+            try
+            {
+               con.close();
+            }
+            catch (Exception ex)
+            {
+               ex.printStackTrace(System.err);
+            }
+         }
+      }
+   }
 
-   @BeforeMethod
-   @Override
-   public void begin() 
+   protected class DataSetOperation
    {
-       super.begin();
+      String dataSetLocation;
+      ReplacementDataSet dataSet;
+      DatabaseOperation operation;
+
+      /**
+       * Defaults to <tt>DatabaseOperation.CLEAN_INSERT</tt>
+       *
+       * @param dataSetLocation location of DBUnit dataset
+       */
+      public DataSetOperation(String dataSetLocation)
+      {
+         this(dataSetLocation, DatabaseOperation.CLEAN_INSERT);
+      }
+
+      /**
+       * Defaults to <tt>DatabaseOperation.CLEAN_INSERT</tt>
+       */
+      public DataSetOperation(String dataSetLocation, String dtdLocation)
+      {
+         this(dataSetLocation, dtdLocation, DatabaseOperation.CLEAN_INSERT);
+      }
+
+      public DataSetOperation(String dataSetLocation, String dtdLocation, DatabaseOperation operation)
+      {
+         log.debug(">>> Preparing dataset: " + dataSetLocation + " <<<");
+
+         // Load the base dataset file
+         InputStream input = Thread.currentThread().getContextClassLoader().getResourceAsStream(dataSetLocation);
+         try
+         {
+            InputStream dtdInput = null;
+            if (dtdLocation != null)
+            {
+               dtdInput = Thread.currentThread().getContextClassLoader().getResourceAsStream(dtdLocation);
+            }
+            if (dtdInput == null)
+            {
+               this.dataSet = new ReplacementDataSet(new FlatXmlDataSet(input));
+            }
+            else
+            {
+               this.dataSet = new ReplacementDataSet(new FlatXmlDataSet(input, dtdInput));
+            }
+         }
+         catch (Exception ex)
+         {
+            throw new RuntimeException(ex);
+         }
+         this.dataSet.addReplacementObject("[NULL]", null);
+         if (binaryDir != null)
+         {
+            this.dataSet.addReplacementSubstring("[BINARY_DIR]", getBinaryDirFullpath().toString());
+         }
+         this.operation = operation;
+         this.dataSetLocation = dataSetLocation;
+      }
+
+
+      public DataSetOperation(String dataSetLocation, DatabaseOperation operation)
+      {
+         this(dataSetLocation, null, operation);
+      }
+
+      public IDataSet getDataSet()
+      {
+         return dataSet;
+      }
+
+      public DatabaseOperation getOperation()
+      {
+         return operation;
+      }
+
+      public void execute(IDatabaseConnection connection)
+      {
+         try
+         {
+            this.operation.execute(connection, dataSet);
+         }
+         catch (Exception ex)
+         {
+            throw new RuntimeException(ex);
+         }
+      }
+
+      @Override
+      public String toString()
+      {
+         // TODO: This is not pretty because DBUnit's DatabaseOperation doesn't implement toString() properly
+         return operation.getClass() + " with dataset: " + dataSetLocation;
+      }
    }
 
-   @AfterMethod
-   @Override
-   public void end() 
+   // Subclasses can/have to override the following methods
+
+   /**
+    * Override this method if you want to provide your own DBUnit <tt>IDatabaseConnection</tt> instance.
+    * <p/>
+    * If you do not override this, default behavior is to use the * configured datasource name and
+    * to obtain a connection with a JNDI lookup.
+    *
+    * @return a DBUnit database connection (wrapped)
+    */
+   protected IDatabaseConnection getConnection()
    {
-       super.end();
+      try
+      {
+         if (datasourceJndiName == null)
+         {
+            throw new RuntimeException("Please set datasourceJndiName TestNG property");
+         }
+
+         DataSource datasource = ((DataSource) getInitialContext().lookup(datasourceJndiName));
+
+         // Get a JDBC connection from JNDI datasource
+         Connection con = datasource.getConnection();
+         IDatabaseConnection dbUnitCon = new DatabaseConnection(con);
+         editConfig(dbUnitCon.getConfig());
+         return dbUnitCon;
+      }
+      catch (Exception ex)
+      {
+         throw new RuntimeException(ex);
+      }
    }
-   
+
+   /**
+    * Override this method if you aren't using HSQL DB.
+    * <p/>
+    * Execute whatever statement is necessary to either defer or disable foreign
+    * key constraint checking on the given database connection, which is used by
+    * DBUnit to import datasets.
+    *
+    * @param con A DBUnit connection wrapper, which is used afterwards for dataset operations
+    */
+   protected void disableReferentialIntegrity(IDatabaseConnection con)
+   {
+      try
+      {
+         if (database.equals(HSQL))
+         {
+            con.getConnection().prepareStatement("set referential_integrity FALSE").execute(); // HSQL DB
+         }
+         else if (database.equals(MYSQL))
+         {
+            con.getConnection().prepareStatement("set foreign_key_checks=0").execute(); // MySQL > 4.1.1
+         }
+      }
+      catch (Exception ex)
+      {
+         throw new RuntimeException(ex);
+      }
+   }
+
+   /**
+    * Override this method if you aren't using HSQL DB.
+    * <p/>
+    * Execute whatever statement is necessary to enable integrity constraint checks after
+    * dataset operations.
+    *
+    * @param con A DBUnit connection wrapper, before it is used by the application again
+    */
+   protected void enableReferentialIntegrity(IDatabaseConnection con)
+   {
+      try
+      {
+         if (database.equals(HSQL))
+         {
+            con.getConnection().prepareStatement("set referential_integrity TRUE").execute();  // HSQL DB
+         }
+         else if (database.equals(MYSQL))
+         {
+            con.getConnection().prepareStatement("set foreign_key_checks=1").execute(); // MySQL > 4.1.1
+         }
+      }
+      catch (Exception ex)
+      {
+         throw new RuntimeException(ex);
+      }
+   }
+
+   /**
+    * Override this method if you require DBUnit configuration features or additional properties.
+    * <p/>
+    * Called after a connection has been obtaind and before the connection is used. Can be a
+    * NOOP method if no additional settings are necessary for your DBUnit/DBMS setup.
+    *
+    * @param config A DBUnit <tt>DatabaseConfig</tt> object for setting properties and features
+    */
+   protected void editConfig(DatabaseConfig config)
+   {
+      if (database.equals(HSQL))
+      {
+         // DBUnit/HSQL bugfix
+         // http://www.carbonfive.com/community/archives/2005/07/dbunit_hsql_and.html
+         config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new DefaultDataTypeFactory()
+         {
+            @Override
+            public DataType createDataType(int sqlType, String sqlTypeName)
+                  throws DataTypeException
+            {
+               if (sqlType == Types.BOOLEAN)
+               {
+                  return DataType.BOOLEAN;
+               }
+               return super.createDataType(sqlType, sqlTypeName);
+            }
+         });
+      }
+   }
+
+   /**
+    * Resolves the binary dir location with the help of the classloader, we need the
+    * absolute full path of that directory.
+    *
+    * @return URL full absolute path of the binary directory
+    */
+   protected URL getBinaryDirFullpath()
+   {
+      if (binaryDir == null)
+      {
+         throw new RuntimeException("Please set binaryDir TestNG property to location of binary test files");
+      }
+      return getResourceURL(binaryDir);
+   }
+
+   protected URL getResourceURL(String resource)
+   {
+      URL url = Thread.currentThread().getContextClassLoader().getResource(resource);
+      if (url == null)
+      {
+         throw new RuntimeException("Could not find resource with classloader: " + resource);
+      }
+      return url;
+   }
+
+   protected byte[] getBinaryFile(String filename) throws Exception
+   {
+      if (binaryDir == null)
+      {
+         throw new RuntimeException("Please set binaryDir TestNG property to location of binary test files");
+      }
+      File file = new File(getResourceURL(binaryDir + "/" + filename).toURI());
+      InputStream is = new FileInputStream(file);
+
+      // Get the size of the file
+      long length = file.length();
+
+      if (length > Integer.MAX_VALUE)
+      {
+         // File is too large
+      }
+
+      // Create the byte array to hold the data
+      byte[] bytes = new byte[(int) length];
+
+      // Read in the bytes
+      int offset = 0;
+      int numRead;
+      while (offset < bytes.length
+            && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0)
+      {
+         offset += numRead;
+      }
+
+      // Ensure all the bytes have been read in
+      if (offset < bytes.length)
+      {
+         throw new IOException("Could not completely read file " + file.getName());
+      }
+
+      // Close the input stream and return bytes
+      is.close();
+      return bytes;
+   }
+
+   /**
+    * Implement this in a subclass.
+    * <p/>
+    * Use it to stack DBUnit <tt>DataSetOperation</tt>'s with
+    * the <tt>beforeTestOperations</tt> and <tt>afterTestOperations</tt> lists.
+    */
+   protected abstract void prepareDBUnitOperations();
+
+
 }




More information about the seam-commits mailing list