Last week, I started using EasyMock to test methods that call an Oracle database’s stored procedures. Typically, those methods receive an object implementing the java.sql.Connection interface.
We later get a CallableStatement out of the Connection, and start calling methods on the statement in order to pass parameters to the stored procedure before calling the statement’s execute method.
In one particular case, we were trying to pass an Oracle ARRAY to the stored procedure. For this, we need to call the setARRAY method on the statement. The problem is that setARRAY is not part of the CallableStatement interface but is specific to the OraclePreparedStatement interface.
The documented way of working this out is to cast the callable statement into an OraclePreparedStatement and then call setARRAY.
public void saveArrayToDatabase(
Connection connection, ARRAY myArray) throws SQLException
{
CallableStatement statement =
connection.prepareCall("{call my_pkg.my_function(?,?)}");
statement.setLong(1, myId);
((OraclePreparedStatemement)statement).setARRAY(2, myArray);
statement.execute();
}
I can’t really say I’m happy with this cast, but that’s how it work, so just add a
// They made me do it
comment and move on.
Now, to test this method we need a Connection object whose prepareCall implementation returns a valid CallableStatement object (otherwise, the method would not execute successfully).
I initially created two mock objects this way in the JUnit test:
Connection mockConnection =
createMock("mockConnection", Connection.class);
final CallableStatement mockCallableStatement =
createMock("mockCallableStatement", CallableStatement.class);
Then, to make the prepareCall method on the mockConnection object return the callable statement mock object, I used an “expectation” and EasyMock’s IAnswer interface:
expect(mockConnection.prepareCall("{call my_pkg.my_function(?,?)}"))
.andAnswer(new IAnswer<CallableStatement>() {
public CallableStatement answer() throws Throwable {
return mockCallableStatement;
}
});
More EasyMock expectations can be defined, but the rubber really meets the road when, in the JUnit test, the method under test is called:
dbproxy.saveArrayToDatabase(mockConnection, myTestArray);
And unfortunately, this call turns out to be problematic. It fails miserably on a ClassCastException.
The mockCallableStatement object that is returned by the mockConnection object when the test runs cannot be cast into an OraclePreparedStatement, which makes sense.
In production, the Connection object is actually an Oracle specific object, and its prepareCall returns an object that also implements the OraclePreparedStatement interface, but it’s not the case here (we’re testing in isolation – no Oracle implementation is involved).
How to implement the test then?
Trying to have the IAnswer implementation return an OraclePreparedStatement doesn’t work.
I thought about creating a “nice” Connection mock object and completely foregoing the creation of a CallableStatement, but the method under test needs it and would throw a NullPointerException when calling a CallableStatement method (such as execute).
The best solution is to have a mock object implement both the CallableStatement and the OraclePreparedStatement interface. This would make the cast work.
EasyMock doesn’t allow mock objects to implement more than one interface but fortunately, there is a way around this: create a new interface that extends the two interfaces:
private interface HybridStatement
extends CallableStatement, OraclePreparedStatement {};
Then, it is simply a matter of creating a mock object that implements the HybridStatement interface instead of implementing the CallableStatement interface:
Connection mockConnection =
createMock("mockConnection", Connection.class);
final HybridStatement mockHybridStatement =
createMock("mockHybridStatement", HybridStatement.class);
expect(mockConnection.prepareCall("{call my_pkg.my_function(?,?)}"))
.andAnswer(new IAnswer<CallableStatement>() {
public CallableStatement answer() throws Throwable {
return mockHybridStatement;
}
});
In retrospect, this is quite logical. Even though CallableStatement and OraclePreparedStatement are not directly related (they only extend the same interface), the statement object given by Oracle implement both interfaces.
I can’t believe that it took me several hours to find what in the end is a simple and fairly logical solution.
I hope this article will help someone.