I changed some method somewhere in our code which shouldn't have caused any weird test failures, but JMock seems to think otherwise.
I boiled the issue down to the minimal amount of cruft, which looks something like this:
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Test;
public class TestMocking {
@Test
public void test() {
Mockery mockery = new Mockery() {{
setImposteriser(ClassImposteriser.INSTANCE);
}};
final Owner owner = mockery.mock(Owner.class);
final RealThing thing = mockery.mock(RealThing.class, "thing");
mockery.checking(new Expectations() {{
oneOf(owner).getThing(); will(returnValue(thing));
oneOf(thing).method(); will(returnValue(Collections.emptyList()));
}});
owner.getThing().method();
mockery.assertIsSatisfied();
}
public static interface Owner {
BaseThing getThing();
}
public static interface BaseThing {
Collection<?> method();
}
public static interface RealThing extends BaseThing {
List<?> method();
}
}
(Edit: This now uses a ClassImposteriser even though there are no classes anymore, because I wanted to demonstrate that you could run the exact same code without that imposteriser and the test would pass.)
The result of running this:
unexpected invocation: thing.method()
expectations:
expected once, already invoked 1 time: owner.getThing(); returns <thing>
expected once, never invoked: thing.method(); returns <[]>
what happened before this:
owner.getThing()
So there you go, "unexpected" thing.method() when the expected thing.method() was never called. I have previously seen this occur when multi-threaded classes are under test against mock objects, but this time it's all happening in a single thread. It's like JMock is somehow returning a different mock object from the first method call, even though I mocked no such object.
If I remove the overridden method which is returning a more specific type then it goes away, but I obviously can't do that. Likewise, if I remove the use of ClassImposteriser, the problem goes away, but one of the objects I'm mocking in the real test is someone else's class. I guess I could try using two Mockery instances in the one test, but aside from that I'm out of ideas.