Monday, November 22, 2010

Advanced Mocking: Capturing State with Answer and Captors


Hmmm… I really need to mock out java.sql.ResultSet, I know I’ll just subclass it with a hand-rolled mock. Oh-no, the default implementation without method bodies is 650 lines of code long. WTF? Did you know there are 197 public methods on the java.sql.ResultSet interface? This is an absurdly large interface, possibly the largest in the JDK (if not drop a comment, I’d love to know what the largest is). The workaround is to throw one together in Mockito (or your mock framework of choice). My implementation turned out to be 21 lines of code long. Not bad at all. To do it I creatively used ArgumentCaptors, Answer objects, and anonymous inner classes to capture state. The samples below are in Java, but they would be a little nicer in Groovy.


Anyway, here is the API I wanted to end up with… specify a String List for the column names, and then give any number of Object Lists for the data.


ResultSet resultSet = makeResultSet(
Arrays.asList("Id", "Name" , "Birthday" ),
Arrays.asList( 1 , "Hamlet", new Date(9999L)),
Arrays.asList( 2 , "Andres", new Date(8888L)),
Arrays.asList( 3 , "Dierk" , new Date(7777L))
);

List<Person> result = mapper.toPerson(resultSet);
assertEquals(3, result.size());
... // assertions left to your imagination


As you can see, the makeResultSet method creates a ResultSet object. ResultSet is part Map implementation and part iterator. As a Map, you can ask things like getString(“columnName”) and get a String result for a named column. Tons of data types are supported. As an Iterator, you can call next() to advance the recordset cursor, which tells you if there is a next record and also allows you to access the next record’s contents. It’s a pretty stateful object.

So here is how I implemented it in Mockito. We’ll dissect it below.


private static ResultSet makeResultSet(
final List<String> aColumns,
final List... rows) throws Exception {

ResultSet result = Mockito.mock(ResultSet.class);
final AtomicInteger currentIndex = new AtomicInteger(-1);

Mockito.when(result.next()).thenAnswer(new Answer() {
public Object answer(InvocationOnMock aInvocation) throws Throwable {
return currentIndex.incrementAndGet() < rows.length;
}
});

final ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class);
Answer rowLookupAnswer = new Answer() {
public Object answer(InvocationOnMock aInvocation) throws Throwable {
int rowIndex = currentIndex.get();
int columnIndex = aColumns.indexOf(argument.getValue());
return rows[currentIndex.get()].get(columnIndex);
}
};
Mockito.when(result.getString(argument.capture())).thenAnswer(rowLookupAnswer);
Mockito.when(result.getShort(argument.capture())).thenAnswer(rowLookupAnswer);
Mockito.when(result.getDate(argument.capture())).thenAnswer(rowLookupAnswer);
Mockito.when(result.getInt(argument.capture())).thenAnswer(rowLookupAnswer);
Mockito.when(result.getTimestamp(argument.capture())).thenAnswer(rowLookupAnswer);
return result;
}

The basic flow is, create the mock ResultSet, mock out the iteration/cursor functionality, then mock out the row/column lookup functionality. Let’s take it from top to bottom.


1. static – I make private methods static. Almost always. It makes refactoring and moving the method so simple. Plus, you can easily analyze the dependencies of the method. They are all right there in the parameter list, no syntax highlighter needed. Moving on…


2. varargs – In unit test helper methods I use varargs a lot. It makes a really nice API for creating data. And remember, varargs is just syntactic sugar for a Java Array. In this example, rows is an Array of Lists. In production code you’d always want to check this parameter for null.


3. final – The parameters are final because we’ll need to reference them from within anonymous inner classes.


4. AtomicInteger – The result set functions as a cursor so there is always a current record. We model this as an integer and the current record is just the index into the rows array. It’s Atomic and final so that we can reference and modify it from within the anonymous inner classes.


5. next() and Answer – The Mockito Answer interface is a high powered “thenReturn” statement. With “thenReturn” you can only return simple objects, with Answer you get to do the same with an anonymous inner class. Think of it as a “thenReturn” factory. Notice, next() returns false when the currentIndex advances beyond the end of the rows Array. And next() calls increment the index to advance the record pointer. When I say “capturing state”, the AtomicInteger is the state and it is captured within the Answer instance. It’s good to know the Atomic family of classes.


6. Captore and Answer for Row/Column Lookup – Now we combine an ArgumentCaptor and another Answer object to implement the getString/getInt/getX functionality. The ArgumentCaptor is an object used to capture parameters sent to methods. It can tell us whether “Birthday” or “Name” was passed to our method. It wraps around the parameter and allows you to call it back later. Again, we use an Answer, combined this time with the Captor to return a row/column value. Our rowIndex is just the currentIndex pointer, and the columnIndex is the index of the parameter name (“Birthday” or “Name”) within the column list. From here it is just a multidimensional “array” access to get our cell value. Woot.


7. when() and the rowLookupAnswer – the rowLookupAnswer can be reused for any datatype in the ResultSet. This isn’t a complete list but was everything I needed.


The Point?

Mock object frameworks are very much about creating objects without the usual constructors and concrete implementations. For my project, Mockito was insanely useful without even using the assertions and verifications that normally go with mocks. Maybe the code is a little complex, I admit, but at least it is short. And as long as you understand Captors and Answers then it really shouldn’t be a problem.


Happy Mocking!