| Test Outline S2 Test Reference Creating a Mock Test Using a Mock Test Creating Test Class with S2Unit S2Unit Functionalities Creating an Excel File Example Mock Testing with a Database ## Test Outline S2Container is able remove dependencies by using only interfaces to communicate between components. This, however, creates a bottleneck during program development because interfaces must be created before testing can start. Seasar2 circumvents this problem by allowing mock interfaces to be easily created. Because implementations can be replaced by mock objects, testing can begin as components are created. Furthermore, S2Unit allows unit testing using containers and there is a functionality to test using database with Excel files.。 ## S2 Test Reference ### Creating a Mock Test org.seasar.framework.aop.interceptors.MockInterceptor is used to define a mock interface. MockInterceptor is an Interceptor offered by S2AOP. By using the following methods in MockInterceptor, mock interface may be set, interface may be called, and arguments may be checked. public void setReturnValue(String methodName, Object returnValue) Description: Sets return value to an interface method Arg 1: Interface method name to return a value Arg 2: Value to be returned by argument 1 public void setReturnValue(Object returnValue) Description: Set return value to all interface methods Arg 1: Value to be returned. All calls to a method will return this value. Usually, this method is used to set a return value when only interface method is called. public boolean isInvoked(String methodName) Description: Used to check to determine if interface method was called Arg 1: Name of interface method to check if it was called Returns: boolean value indicating whether the interface method was called public Object[] getArgs(String methodName) Description: Gets arguments passed to an interface method Arg 1: Name of interface method to get argment values from Returns: Object[] array containing argument values of an interface method ### Using a Mock Test To create an mock interface, values are set using an MockInterceptor aspect. For example, let's assume thiere is a following interface:
    public interface Hello {
        public String greeting();
        public String echo(String str);
    }
Following code is used to create a mock interface that returns "Hello" when greeting() method is called and returns "Hoge" when echo() method is called.
    MockInterceptor mi = new MockInterceptor();
    mi.setReturnValue("greeting", "Hello");
    mi.setReturnValue("echo", "Hoge");
    Hello hello = mi.createProxy(Hello.class);
Above code may be written as the following component definition.
    <component class="examples.aop.Hello">
        <aspect>
            <component class="org.seasar.framework.aop.interceptors.MockInterceptor">
                <initMethod name="setReturnValue">
                    <arg>"greeting"</arg>
                    <arg>"Hello"</arg>
                </initMethod>
                <initMethod name="setReturnValue">
                    <arg>"echo"</arg>
                    <arg>"Hoge"</arg>
                </initMethod>
            </component>
        </aspect>
    </component>
Refer to Mock Examples for detailed information on creating mock interfaces. ### Creating Test Class with S2Unit Test classes are created by extending org.seasar.extension.unit.S2TestCase class. Test methods are the same as thos in JUnit because S2TestCase class is extended from JUnit. ### S2Unit Functionalities Following functions are offered to make unit testing with S2Container easier. Automatic creation of S2Container When S2Container is constructed, test method (testXxx) is also constructed. It is not neccessary to write S2ContainerFactory.create(PATH) to construct a S2Container. register(),getComponent(),include() methods register(),getComponent(),include() methods are available to S2Container. Omitting PATH in include It is possible to omit PATH in include() when path are the same as that is a test class. For example, if aaa.bbb.CccTest class include() aaa/bbb/hoge.dicon, it is possible to just write include("hoge.dicon"). Automatic setting of variables TestCase fields that are not static nor final are automatically set with underscore character (_) omitted from their names TestCase fields with interface type variable definitions are automatically set by S2Container. Values automatically assigned are cleared (set to null) afer test method ends. setUpXxx(),tearDownXxx() methods If test method(testXxx) is defined with corresponding setUpXxx() and tearDownXxx() methods, it is invoked after setUp() and before tearDown() are invoked. It is easy to initialize and finalize on each test method. Following features are available when conducting a test using a database. Transactions By ending test method names with Tx(e.g.testXxxTx) in include("j2ee.dicon") file, transaction is initiated just before the test method and rolled back after the test method is invoked. Thus, it is unnecessary to manually rollback data after each test. assertEquals() method assertEquals() method may be used to compare expected DataSet result with Map, Map List, Bean, and Bean List. readXls() method DataSet expected = readXls("verification.xls") statement reads verification data from Excel into a DataSet. If PATH of a Excel verification data is in the same package as a class, path may be omitted Refer to Creating an Excel File on steps to create a verification data. reload() method reload(DataSet) is used to reload data by a primary key and to obtain a new DataSet. Update test may be easily done by entering expected updated values into the Excel sheet. DataSet expected = readXls("verification.xls"); assertEquals(expected, reload(expected); readXlsWriteDb(),readXlsAllReplaceDb() methods Verification data entered into Excel sheet are inserted into a database by readXlsWriteDb("verification.xls") or readXlsAllReplaceDb("verification.xls"). If verification Excel file is in the same package as the test class, package path may be omitted. Usually, to rollback data to their original values, readXlsWriteDb() and readXlsAllReplaceDb() are executed in the first lines of testXxxTx(). Furthermore, these methods insert data after deleting in reverse order of Excel sheet order. When using readXlsAllReplaceDb() method, it may be neccessary to create an empty sheet to avoid foreign data constraint error. For example, if foreign key in table A reference table B, it is neccessary to create an Excel sheet for table B even when there is only data fro table A. It is, also, necessary to create Excel sheet for table A before a sheet for table B. ### Creating an Excel File Verification Excel file is created as follows. Excel file is usually created in the same directory as the test class file. 1. Sheet name Table name 2. First row Column name Sheets with N:1 table mapping should have a name "ColumnName_RelationShipNumber". 3. Other rows Test data To generate verification Excel data from a database, following class may be used. Class to read content from a database into a DataSet Output DataSet to an Excel file Class to read content from a database into a DataSet Class Name org.seasar.extension.dataset.impl.SqlReader Methods public void addTable(String tableName) Description: Read entire data from a specified table into a DataSet Arg 1: Table name of a table to get the data public void addTable(String tableName, String condition) Description: Get data from the specified table matching the specified condition into a DataSet Arg 1: Table name of a table to get te data Arg 2: Condition to match when getting a data public void addSql(String sql, String tableName) Description: Get data from the specified table matching the specified SQL statement into a DataSet Arg 1: Select statement to get the data Arg 2: Table name of a table to get the data public DataSet read() Description: Return DateSet that was read Return: DataSet from addTable() method or addSql() method. Class to output DataSet to an Excel file Class Name org.seasar.extension.dataset.impl.XlsWriter Methods (Constructor)public XlsWriter(String path) Description: Specify file the path of Excel file to generate data Arg 1: File path of output Excel file public void write(DataSet dataSet) Description: Output data from DataSet to an Excel file specified in the XlsWriter() Arg 1: Result DataSet Refer to an example inTesting with a Database for further detailed information. ## Example To test the following example, please setup the environment following steps outlined in Setup. ### Mock Let's create a mock interface using MockInterceptor that is made available by S2 and than test using S2Unit to verify that method and their arguments are correct. Required files are as follows: Interface(Hello.java) dicon file (Hello.dicon) defining mock to an interface Class (HelloTest.java) to test created interface ##### Creating Interface Create method (greeting) with no argument and String return type. Create method (echo) with 1 argument and String return type. Hello.java
    package examples.aop.mockinterceptor;

    public interface Hello {

        public String greeting();

        public String echo(String str);

    }
##### Creating dicon File Mock will return "Hello" when greeting() method is called and "Hoge" when echo() method is called. MockInterceptor is defined in a component. name attribute is set to helloMockInterceptor. Set argument as specified by a mock by method injunction to setReturnValue() method in MockInterceptor. Interface is defined in a component. Specify MockInterceptor defined in a component to an aspect tag. Hello.dicon
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
    "http://www.seasar.org/dtd/components.dtd">
    <components>
        <component name="mockInterceptor"
                   class="org.seasar.framework.aop.interceptors.MockInterceptor">
            <initMethod name="setReturnValue">
                <arg>"greeting"</arg>
                <arg>"Hello"</arg>
            </initMethod>
            <initMethod name="setReturnValue">
                <arg>"echo"</arg>
                <arg>"Hoge"</arg>
            </initMethod>
        </component>
        <coponent class="examples.aop.mockinterceptor.Hello">
            <aspect>
                mockInterceptor
            </aspect>
        </component>
    </components>
##### Creating Test Class Inherit S2TestCase Declare variables so examples.aop.Hello and org.seasar.framework.aop.interceptors.MockInterceptor in dicon file will automatically be set. Use MockInterceptor#isInvoked(String methodName) to verify method was called. use MockInterceptor#getArgs(String methodName) to verify values of method's arguments are correct. HelloTest.java
    package examples.aop.mockinterceptor;

    import org.seasar.extension.unit.S2TestCase;
    import org.seasar.framework.aop.interceptors.MockInterceptor;

    public class HelloTest extends S2TestCase{
        //omitted path because in same package as test class
        private static String PATH = "Hello.dicon";

        //auto set variable
        private Hello hello ;

        private MockInterceptor mi ;

        public void testHello() throws Exception{

    	//check if dicon file defined mock interface correctly
    	assertEquals("Hello", hello.greeting());
    	assertEquals("Hoge", hello.echo("test"));

    	hello.echo("Hello");
    	//check if echo()method is called
    	assertEquals(true, mi.isInvoked("echo"));

    	//check if argument of echo() method is "Hello"
    	assertEquals("Hello", mi.getArgs("echo")[0]);

        }

        protected void setUp() throws Exception {
              //S2Container include() method
    	include(PATH);
        }

        protected void tearDown() throws Exception {
        }

        public HelloTest(String arg0) {
    	super(arg0);
        }

        public static void main(String[] args) {
    	junit.textui.TestRunner.run(HelloTest.class);
        }

    }
Components are automatically constructed by S2TestCase so only necessary processes needs to be in a test method. ##### Results If "OK (Test Count test)" is displayed, assertEquals() method executed as expected.
    .
    Time: 1.643

    OK (1 test)
Example files may be found under seasar2/src/examples/aop/mockinterceptor directory. ### Testing with a Database Following example uses HSQLDB. Refer to Setup to setup and start HSQLDB before trying out this example. This example uses the following EMP(Employee) table and DEPT(Department) table. Table: EMP(Employee)
Column Name | Label | Type | NotNull | Primary Key
--- | --- | --- | --- | ---
EMPNO | Employee Number | NUMBER | 〇 | 〇
ENAME | Employee Name | VARCHAR2 |  |
JOB | Job | VARCHAR2 |  |
MGR | Manager | NUMBER |  |
HIREDATE | Hire Date | DATE |  |
SAL | Salary | NUMBER |  |
COMM | Commission | NUMBER |  |
DEPTNO | Department Number | NUMBER |  |
Table:DEPT(Department)
Column Name | Label | Type | NotNull | Primary Key
--- | --- | --- | --- | ---
DEPTNO | Department Number | NUMBER | 〇 | 〇
DNAME | Department Name | VARCHAR2 |  |
LOC | Location | VARCHAR2 |  |
VERSIONNO | Version Number | NUMBER |  |
This example used S2JDBC, the most simplistic way to execute a SQL statement. Test verifies DAO result from a query on EMP table based on employee number. DAO source that is used can be found in the seasar2/src/examples/unit/ directory. This example will query EMP table for employee number 9900 and obtain a result data set that is a join on EMP table and a DEPT table. Testing steps are as follows: Create Excel sheets with data for EMP table and DEPT table Create an Excel sheet with verification data to compare test results Create test class and then execute it 1.Create Excel sheets with data for EMP table and DEPT table Excel sheets have table name as their names, table column names in their first rows, and data to be used for a test in following rows. In this example, however, Excel sheets will be generated from the database. Following files are used to generate Excel sheets with test data dicon file (Db2Excel.dicon) to output database data to Excel. Java program file (Db2Excel.java) to use the dicon file to output database data to Excel. Create dicon file Include j2ee.dicon Define component in SqlReaderclass. In this example, only one record will be selected - employee number 7788 record from the EMP table and department number 20 from the DEPT table. Define component in XlsWriter class. Specify file path to output by constructor injection. Db2Excel.dicon
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
    "http://www.seasar.org/dtd/components.dtd">
    <components>
        <include path="j2ee.dicon"/>
        <component class="org.seasar.extension.dataset.impl.SqlReader">
            <initMethod>#self.addTable("emp", "empno = 7788")</initMethod>
            <initMethod>#self.addTable("dept", "deptno = 20")</initMethod>
        </component>
        <component class="org.seasar.extension.dataset.impl.XlsWriter" instance="prototype">
            <arg>"../src/test/examples/unit/getEmployeePrepare.xls"</arg>
        </component>
    </components>
Creating an Executable File Create a container using org.seasar.framework.container.S2Container#create() method with the first argument set to path of the created dicon file (Db2Excel.dicon) Set the first argument of org.seasar.framework.container.S2Container#getComponent() method to class name（SqlReader.class) registered in the component. Set the first argument of org.seasar.framework.container.S2Container#getComponent() method to class name（XlsWriter.class) registered in the component. Execute after setting the first argument of XlsWriter#write() got from the container to SqlReader#read(). Db2Excel.java
    package test.examples.unit;

    import org.seasar.extension.dataset.impl.SqlReader;
    import org.seasar.extension.dataset.impl.XlsWriter;
    import org.seasar.framework.container.S2Container;
    import org.seasar.framework.container.factory.S2ContainerFactory;

    public class Db2Excel {

        private static final String PATH =
            "test/examples/unit/Db2Excel.dicon";

        public static void main(String[] args) {
            S2Container container = S2ContainerFactory.create(PATH);
            container.init();
            try {
                SqlReader reader = (SqlReader)
                    container.getComponent(SqlReader.class);
                XlsWriter writer = (XlsWriter)
                    container.getComponent(XlsWriter.class);
                writer.write(reader.read());
            } finally {
                container.destroy();
            }
        }
    }
Result Check whether if the content of file ../src/test/examples/unit/getEmployeePrepare.xls is like those below: Inquiry of employee number 9900 should return a result that is a result of a join of employee number 9900 row from an EMP table and department number 99 from DEPT table. Thus, enter EMPNO as 9900 and DEPTNO as 99 in Excel's emp sheet and DEPTNO as 99 in the dept sheet. Save the Excel document to a file. 2.Create an Excel sheet with verification data to compare test results Create data to verify the result. This data may be manually created but it can also be generated using steps similar to generate test data. Followings are files necessary to generate verification data: dicon file (Db2Excel.dicon) that was used to generate test data can be modified slight so it will now generate verification data. Creating dicon file Redefine a component in SqlReader class using addSql() method in SqlReaderclass. Change output file path using component definition in XlsWriter. Db2Excel.dicon
    <components>
        <include path="j2ee.dicon"/>
        <component class="org.seasar.extension.dataset.impl.SqlReader">
            <initMethod>
            #self.addSql("SELECT e.empno, e.ename, e.deptno, d.dname
            FROM emp e, dept d WHERE empno = 7788 AND e.deptno = d.deptno", "emp")
            </initMethod>
        </component>
        <component class="org.seasar.extension.dataset.impl.XlsWriter"
            instance="prototype">
            <arg>"../src/test/examples/unit/getEmployeeResult.xls"</arg>
        </component>
    </components>
Execute Db2Excel.java file created earlier. Results Verify that content of ../src/test/examples/unit/getEmployeeResult.xls file are like those below: This example assumes a query on EMP table on employee number 9900 will return a result that is a join of employee number 9900 in EMP table and department number 99 in DEPT table. Thus, enter 9900 as EMPNO in a Excel's emp sheet and 99 as DEPTNO. 3.Creating Test Classes to Conduct a Test After creating Excel sheet containing data for EMP table and DEPT table and another Excel sheet containing test verification data, test class to actually call Dao and do data verification should be created. Creating a Test Class Extend S2TestCase Include dicon file in setUp() Append test method name with Tx to rollback database after using S2Unit transaction control to add from Excel sheet to EMP table and DEPT table. Insert database data entered in Excel sheet into database using readXlsWriteDb() method. Set verification data Excel sheet into DataSet using readXls() method. Use assertEquals() method to compare data read into DataSet with data from Dao. EmployeeDaoImplTest.java
    package test.examples.unit;

    import org.seasar.extension.dataset.DataSet;
    import org.seasar.extension.unit.S2TestCase;

    import examples.unit.Employee;
    import examples.unit.EmployeeDao;

    public class EmployeeDaoImplTest extends S2TestCase {

        private EmployeeDao dao_;

        public EmployeeDaoImplTest(String arg0) {
            super(arg0);
        }

        public void setUp() {
            include("examples/unit/EmployeeDao.dicon");
        }

        public void testGetEmployeeTx() throws Exception {
            readXlsWriteDb("getEmployeePrepare.xls");
            Employee emp = dao_.getEmployee(9900);
            DataSet expected = readXls("getEmployeeResult.xls");
            assertEquals("1", expected, emp);
        }

        public static void main(String[] args) {
            junit.textui.TestRunner.run(EmployeeDaoImplTest.class);
        }
    }
Results If message "OK (Test Count test)" is displayed, assertEquals() method obtained the expected result.
    .DEBUG 2004-10-08 13:10:00,762 [main] Transaction started
    DEBUG 2004-10-08 13:10:05,379 [main] physical connection opened
    DEBUG 2004-10-08 13:10:05,469 [main] logical connection opened
    DEBUG 2004-10-08 13:10:05,990 [main] logical connection closed
    DEBUG 2004-10-08 13:10:06,140 [main] INSERT INTO emp(EMPNO, ENAME, JOB, MGR, HIREDATE,
      SAL, COMM, DEPTNO) VALUES(9900, 'SCOTT2', 'ANALYST', 7566, '1982-12-09 00.00.00',
      3000, null, 99)
    DEBUG 2004-10-08 13:10:06,140 [main] logical connection opened
    DEBUG 2004-10-08 13:10:06,901 [main] logical connection closed
    DEBUG 2004-10-08 13:10:06,911 [main] logical connection opened
    DEBUG 2004-10-08 13:10:07,151 [main] logical connection closed
    DEBUG 2004-10-08 13:10:07,151 [main] INSERT INTO dept(DEPTNO, DNAME, LOC) VALUES(99,
      'RESEARCH2', 'DALLAS')
    DEBUG 2004-10-08 13:10:07,151 [main] logical connection opened
    DEBUG 2004-10-08 13:10:07,151 [main] logical connection closed
    DEBUG 2004-10-08 13:10:07,151 [main] SELECT e.empno, e.ename, e.deptno, d.dname
      FROM emp e, dept d
    WHERE e.empno = 9900 AND e.deptno = d.deptno
    DEBUG 2004-10-08 13:10:07,151 [main] logical connecion opened
    DEBUG 2004-10-08 13:10:07,312 [main] logical connection closed
    DEBUG 2004-10-08 13:10:07,392 [main] transaction rolled back
    DEBUG 2004-10-08 13:10:07,492 [main] physical connection closed

    Time: 13.87

    OK (1 test)
In this way, test result verification data is read from Excel sheet and compared with results from Dao. Furthermore, all data updated in the database during the test are rollbacked so the values are returned to those before the test was conducted. Examples are in seasar2/src/test/examples/unit directory.
Trans. by H.Ozawa | Copyright© 2004-2005, The Seasar Foundation and the others. All rights reserved.