9
votes

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...)

2
perhaps the compiler inlined it?dkatzel

2 Answers

7
votes

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.

5
votes

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.