Monday, July 20, 2009

org.mockito.exceptions.misusing.UnfinishedStubbingException

Mockito is a great tool for mocking. It also showed me how readable Java code can look if done right (readable for Java code that is, of course). But every once in a while Mockito produces one of those very informative error messages and I spend some time figuring out how to avoid the mis-behaving code. (Run-time code generation ftw)

Lets have a look at an example. It "works" with Mockito 1.7. We start with the code to be tested. Have a look at the following class definition, with Opera and Hero being dummy classes. (Full source code available below)

static class CoverCreator {
public Cover print(Opera opera) {
// magic
opera.getHero().prepareToPrint();
// magic
return new Cover();
}
}

Intuitively, CoverCreator produce a cover for a given opera. To be able to do that, it needs, among other things, call a prepare method which returns void.

To test this behavior we define a helper method that creates a mocked opera and stuffs it with another mock of type Hero. Since it is used by many tests, we don't stub any "real" methods here.

private Opera createMockedOpera() {
Opera opera = mock(Opera.class);

Hero hero = mock(Hero.class);
when(opera.getHero()).thenReturn(hero);

return opera;
}

The test in question creates such a mocked Opera object and stubs the prepareToPrint() Method (to do nothing, in this case).

public void testFails() {
// setup
Opera opera = createMockedOpera();
doNothing().when(opera.getHero()).prepareToPrint(); <--

// run
Cover cover = new CoverCreator().print(opera);

// assert
assertNotNull("Correct input must result in a cover object!", cover);
}

When run, this test produces the following UnfinishedStubbingException. The line pointed to by the exception is the highlighted one.

org.mockito.exceptions.misusing.UnfinishedStubbingException:
Unifinished stubbing detected!
E.g. toReturn() may be missing.
Examples of correct stubbing:
when(mock.isOk()).thenReturn(true);
when(mock.isOk()).thenThrow(exception);
doThrow(exception).when(mock).someVoidMethod();
Also make sure the method is not final - you cannot stub final methods.
at UnfinishedStubbingTest.testFails(UnfinishedStubbingTest.java:47)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at junit.framework.TestCase.runTest(TestCase.java:164)
at junit.framework.TestCase.runBare(TestCase.java:130)
at junit.framework.TestResult$1.protect(TestResult.java:106)
at junit.framework.TestResult.runProtected(TestResult.java:124)
at junit.framework.TestResult.run(TestResult.java:109)
at junit.framework.TestCase.run(TestCase.java:120)
at junit.framework.TestSuite.runTest(TestSuite.java:230)
at junit.framework.TestSuite.run(TestSuite.java:225)
at org.eclipse.jdt.internal.junit.runner.junit3.JUnit3TestReference.run(JUnit3TestReference.java:130)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)


This is not so great, especially since it is not immediately clear, why. The argument provided to the method is definitely a mock. This can be verified in the debugger or by extracting the result of opera.getHero() manually.

The problem is that opera in this case is a mock also. At this point, mockito is somehow confused by the mocked call, which is very similar to the usual stubbing pattern when(some_mock.call()).thenReturn(some_return_value).

So, what needs to be done is to extract the mock before. And everything is fine.

public void testSucceeds() {
// setup
Opera opera = createMockedOpera();
Hero hero = opera.getHero();
doNothing().when(hero).prepareToPrint();

// run
Cover cover = new CoverCreator().print(opera);

// assert
assertNotNull("Correct input must result in a cover object!", cover);
}

Full source code here: java, pretty printed html

2 comments:

Sanju Thomas said...

Thanks, it helped.

Sanju Thomas said...
This comment has been removed by the author.