How to mock iBatis SqlMapClient
Posted by Mike Haller
on Monday, December 3. 2007
at 11:08
in Java
When mocking DAOs accessing stored procedures, declaring the expected results becomes a little bit ugly. That's because the expected results needs to be injected by reference into the IN/OUT parameter object, usually a Map. To make things worse, this Map could be created within the class under test, so you need additional effort to mock that, too.My solution is based on EasyMock, Spring Framework and Apache iBatis SqlMap for Java.
The class under test looks like this:
public class SqlmapsFooDao
extends SqlMapClientDaoSupport {
public Collection load(Parameter parameter) {
Map sqlInOut = createSQLParameters(parameter);
return internalLoad(parameter, sqlInOut);
}
protected Map createParameter(Parameter parameter) {
Map sqlInOut = new HashMap();
// Converts parameter into SQL IN/OUT vars using a Map
sqlInOut.put("someVariable", parameter.getFoo());
return sqlInOut;
}
private Collection internalLoad(Parameter parm, Map sqlInOut) {
if (parm.isFirstProc())
{
getSqlMapClientTemplate().
update("executeFirstProcedure", sqlInOut);
return (Collection) sqlInOut.get("cursor");
}
else if (parm.isSecond())
{
getSqlMapClientTemplate().
update("executeSecondProcedure", sqlInOut);
return (Collection) sqlInOut.get("cursor");
}}}
For the sake of readability, I removed a lot (such as generics, exception handling etc.) but the overall structure is kept: the public
load() method which is called by the application, the internal createParameter() method used to convert the domain model Parameter object into SQL variables and the internalLoad() method, which has a little bit of logic.My goal is to test the logic of the internalLoad() method: whether it calls the correct procedure based on the isFirstProcedure() or isSecondProcedure() calls.
Now to the fun part, writing the additional test case for the internLoad() method.
I start by creating a test case:
public void testInternalLoad()
throws Exception {
// 0) Set up the fake result list
// 1) Create EasyMock controls and mocks
// 2) Configure method invocations
// 3) Configure expected calls
// 4) Replay mode
// 5) Run tests
// 6) Verify mocks
// 7) Additional asserts
}
0) Set up the fake result list of the SqlMap execution
final Collection expectedResultList =
new ArrayList();
// TODO: Probably add a fake domain object instance,
// e.g. some DAOs throw FoobarObjectNoFoundExceptions
1) Create EasyMock controls and the mock instances
IMocksControl controlClient = EasyMock.createNiceControl(); SqlMapClient client = controlClient.createMock(SqlMapClient.class); IMocksControl controlSession = EasyMock.createNiceControl(); SqlMapSession session = controlSession.createMock(SqlMapSession.class); IMocksControl controlDataSource = EasyMock.createNiceControl(); DataSource dataSource = controlDataSource.createMock(DataSource.class);
2) Configure implicit method invocations
sqlMapClient.openSession();
controlClient.andReturn(session).anyTimes();
sqlMapClient.getDataSource();
controlClient.andReturn(dataSource).anyTimes();
// Need to inject this map into our class under test
final Map sqlInOut = new HashMap();
SqlmapsFooDao dao = new SqlmapsFooDao() {
@Override
protected Map
createParameter(Parameter parameter) {
// Ignore parameter for tests
return sqlInOut;
}
};
3) Configure the expected method invocations. This is the important part.
sqlMapSession.update("executeFirstProcedure", sqlInOut);
// Simulate what the procedure is doing:
// inject the result from the cursor into the parameter map
sessionControl.andAnswer(new IAnswer(){
public Object answer() {
// Cursor is stored in the input parameter map
sqlInOut.put("cursor", expectedResultList);
return null;
}
});
Attention: You need to use sqlMapSession.update() instead of sqlMapClient.update() when using Spring SqlMapClientTemplate, because an internal SqlMapExecutor uses the session directly.
4) Set to replay mode, that activates the mocks
controlClient.replay(); controlSession.replay(); controlDataSource.replay();
5) Run the test
Collection result = dao.load(new Parameter());
6) Verify that the mocks have been called as expected
controlClient.verify(); // This ensures that "executeFirstProcedure" is called // instead of "executeSecondProcedure" controlSession.verify(); controlDataSource.verify();
7) Do your additional assertions
assertNotNull(result);
And this is what happens when the test is run:
In 5), the DAO instance's public method
load() is called. This will jump into the overwritten createParameter() method, hence sqlInOut.put("someVariable",...) is never called.Instead, the class under test returns the pre-instantiated
sqlInOut Map. That's there the comment // Ignore parameter for tests is in the above code.The class under test will then execute
return internLoad(parameter, sqlInOut") which should lead to a call to getSqlMapClientTemplate().update("executeFirst..."). The SqlMapClientTemplate class is not mocked, but provided by Spring's SqlMapClientDaoSupport super class. The template will delegate the update() method call to the mocked SqlMapSession.SqlMapSession is handled by EasyMock, which in turn will execute our anonymous IAnswer implementation. This implementation will put the fake expected result list into the sqlInOut parameters, under the key cursor. The DAO will then retrieve the result from the map and return it to the application:
return (Collection) sqlInOut.get("cursor");

This is a great article...
I need to use the above for iBatis on Spring framework. The difficulty I face is mocking the SqlMapClientTemplate class which is a Spring implementation.
how can this be done?
Thanks,
Amit
Your class which uses the template should rather use the SqlMapClientOperations, which you can then replace with your mock. What do you think?