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.。
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
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.
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.
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.
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
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.
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.
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.
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().
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.