Thursday, November 11, 2010

Deep thoughts about deep mocks

A deep mock is a mock that has methods that also return mocks. In theory, you could follow a chain of mocks for as long as you wanted.

Deep mocks are supported by both Mockachino and Mockito, with the exception that a method from a mock can't return a mock if the return type is primitive or final.

Another problem with deep mocks was previously generics.
The following would fail in both Mockachino and Mockito:
static interface ListSet extends List<Set> {}
@Test
public void testDeepMockWithClass() {
ListSet mock = mock(ListSet.class, RETURNS_DEEP_STUBS);
Set mock2 = mock.get(0);
}

This would fail on the last line, because the mocking frameworks wouldn't understand that the return type of get(int) should be Set instead of Object.

This has been fixed in Mockachino 0.5.0 (with a fairly small patch, I'm happy to say!) but it is still broken in Mockito 1.8.5.

Now, this particular example takes advantage of the fact that the class we're dealing with has no open generic types. The type is fully bound.

It would be much harder to support something like this:
@Test
public void testDeepMockWithClass() {
List<Set> mock = mock(List<Set>.class, RETURNS_DEEP_STUBS);
Set mock2 = mock.get(0);
}


This is in fact not even legal Java syntax. It's also not valid Java semantics, since the runtime doesn't maintain type information except for some special cases.
So we simplify it into this, which is valid Java syntax:
@Test
public void testDeepMockWithClass() {
List<Set> mock = mock(List.class, RETURNS_DEEP_STUBS);
Set mock2 = mock.get(0);
}

Unfortunately, at the time of the call to mock, we only know about List.class, not that it should have Set as its generic type parameter, so it would be impossible to deduce which type it should create at mock.get(0).

This has two possible solutions. Either create a specific sub interface/class, like we did with ListSet. The other solution is to use the TypeLiteral concept, found in Google Guice:
@Test
public void testDeepMockWithClass() {
List<Set> mock = mock(
new TypeLiteral<List<Set>>() {},
fallback(DEEP_MOCK));
Set mock2 = mock.get(0);
}


Since we're actually creating a new anonymous class here, Java manages to retain the type information. It's unfortunately a bit verbose, but it should still come in handy in the few times you'd need to use deep mocks with generics.