Why does Mockito swallow up stack traces? For example, if I have a
public class Foo
{
public void foo()
{
bar();
}
public void bar()
{
baz();
}
public void baz()
{
throw new RuntimeException();
}
}
and a test such as
public class MockTest
{
@Test
public void test()
{
Mockito.spy(new Foo()).foo();
}
}
the exception thrown always looks like
java.lang.RuntimeException at Foo.baz(Foo.java:17) at MockTest.test(MockTest.java:11) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) at org.junit.runners.ParentRunner.run(ParentRunner.java:309) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
** So, where is all the stuff between
Foo.baz(Foo.java:17) MockTest.test(MockTest.java:11)
?
(The example provided here is a just a simplification -- I'm dealing with a lot more indirections, classes, and so forth. I can't have Mockito swallowing up the critical parts of a partial mock stack trace...)
YES, mockito cleans stacktraces !
The piece of code at work StackTraceFilter
There are different ways to disable that
Since Mockito 1.10.10, provide your own StackTraceCleanerProvider
via the mockito extension mechanism (create a resource file mockito-extensions/org.mockito.plugins.StackTraceCleanerProvider
with the qualified name of your implementation)
Override the cleansStackTrace
method in a custom IMockitoConfiguration
, look there for more information.
This is the magic of proxies.
The javadoc of [Mockito.spy()][1]
states
Creates a spy of the real object. The spy calls real methods unless they are stubbed.
So spy()
returns a mock object, which is a proxy. It is a sub class of Foo
so it inherits the methods, but it wraps their execution in a interceptor method. This method has a try catch block which catches any exception thrown in the actual method invocation. The catch block then uses a ConditionalStackTraceFilter
to clean up the stack trace. To do this, it uses a StackTraceFilter
which in the comments of its filter(..)
method states
/**
* Example how the filter works (+/- means good/bad):
* [a+, b+, c-, d+, e+, f-, g+] -> [a+, b+, g+]
* Basically removes all bad from the middle. If any good are in the middle of bad those are also removed.
*/
The call stack at the invocation of baz()
is something like (super simplified)
at Foo.baz()
at FooPROXY.baz()
at Foo.bar()
at FooPROXY.bar()
at Foo.foo()
at FooPROXY.foo()
at MockTest.test()
All the PROXY
stack trace elements, which are the proxies and the interceptors involved, and everything in between get removed. So you get the result you see.
Note that Junit also cleans it up so as not to show its internals.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With