Repository: mockito/mockito
Issue: InvocationFactory.createInvocation's realmethod cannot throw a Throwable

InvocationFactory.createInvocation takes a Callable that is used to call the real method of the Invocation. If the Invocation throws an Exception the Exception is forwarded from the Callable. Unfortunately Callable can only throw an Exception, not a Throwable.

Hence if the real method throws a Throwable that is neither an Error, RuntimeException or Exception it cannot be forwarded.

I recommend for InvocationFactory.createInvocation to take realMethod argument that can throw a full Throwable.

This is required for fixing: linkedin/dexmaker#64


Issue comments:

Pull request review comments:
Nit: no need to add `final` here
done

Patch that resolved the issue:
diff --git a/src/main/java/org/mockito/internal/invocation/DefaultInvocationFactory.java b/src/main/java/org/mockito/internal/invocation/DefaultInvocationFactory.java
index af8fd3d5fb..03ee54bea1 100644
--- a/src/main/java/org/mockito/internal/invocation/DefaultInvocationFactory.java
+++ b/src/main/java/org/mockito/internal/invocation/DefaultInvocationFactory.java
@@ -14,8 +14,17 @@
 
 public class DefaultInvocationFactory implements InvocationFactory {
 
-    public Invocation createInvocation(Object target, MockCreationSettings settings, Method method, Callable realMethod, Object... args) {
-        RealMethod.FromCallable superMethod = new RealMethod.FromCallable(realMethod);
+    public Invocation createInvocation(Object target, MockCreationSettings settings, Method method, final Callable realMethod, Object... args) {
+        RealMethod superMethod = new RealMethod.FromCallable(realMethod);
+        return createInvocation(target, settings, method, superMethod, args);
+    }
+
+    public Invocation createInvocation(Object target, MockCreationSettings settings, Method method, RealMethodBehavior realMethod, Object... args) {
+        RealMethod superMethod = new RealMethod.FromBehavior(realMethod);
+        return createInvocation(target, settings, method, superMethod, args);
+    }
+
+    private Invocation createInvocation(Object target, MockCreationSettings settings, Method method, RealMethod superMethod, Object[] args) {
         return MockMethodInterceptor.createInvocation(target, method, args, superMethod, settings);
     }
 }
diff --git a/src/main/java/org/mockito/internal/invocation/RealMethod.java b/src/main/java/org/mockito/internal/invocation/RealMethod.java
index cd74d3c440..b7c8c1748f 100644
--- a/src/main/java/org/mockito/internal/invocation/RealMethod.java
+++ b/src/main/java/org/mockito/internal/invocation/RealMethod.java
@@ -5,6 +5,7 @@
 package org.mockito.internal.invocation;
 
 import org.mockito.internal.exceptions.stacktrace.ConditionalStackTraceFilter;
+import org.mockito.invocation.InvocationFactory;
 import org.mockito.invocation.InvocationOnMock;
 
 import java.io.Serializable;
@@ -31,14 +32,23 @@ public Object invoke() {
         }
     }
 
-    class FromCallable implements RealMethod {
+    class FromCallable extends FromBehavior implements RealMethod {
+        public FromCallable(final Callable<?> callable) {
+            super(new InvocationFactory.RealMethodBehavior() {
+                @Override
+                public Object call() throws Throwable {
+                    return callable.call();
+                }
+            });
+        }
+    }
 
-        private static final long serialVersionUID = 47957363950483625L;
+    class FromBehavior implements RealMethod {
 
-        private final Callable<?> callable;
+        private final InvocationFactory.RealMethodBehavior<?> behavior;
 
-        public FromCallable(Callable<?> callable) {
-            this.callable = callable;
+        FromBehavior(InvocationFactory.RealMethodBehavior<?> behavior) {
+            this.behavior = behavior;
         }
 
         @Override
@@ -49,7 +59,7 @@ public boolean isInvokable() {
         @Override
         public Object invoke() throws Throwable {
             try {
-                return callable.call();
+                return behavior.call();
             } catch (Throwable t) {
                 new ConditionalStackTraceFilter().filter(t);
                 throw t;
diff --git a/src/main/java/org/mockito/invocation/InvocationFactory.java b/src/main/java/org/mockito/invocation/InvocationFactory.java
index 8339642215..1803718696 100644
--- a/src/main/java/org/mockito/invocation/InvocationFactory.java
+++ b/src/main/java/org/mockito/invocation/InvocationFactory.java
@@ -8,6 +8,7 @@
 import org.mockito.MockitoFramework;
 import org.mockito.mock.MockCreationSettings;
 
+import java.io.Serializable;
 import java.lang.reflect.Method;
 import java.util.concurrent.Callable;
 
@@ -19,7 +20,7 @@
  * <p>
  * Please don't provide your own implementation of {@link Invocation} type.
  * Mockito team needs flexibility to add new methods to this interface if we need to.
- * If you integrate Mockito framework and you need an instance of {@link Invocation}, use {@link #createInvocation(Object, MockCreationSettings, Method, Callable, Object...)}.
+ * If you integrate Mockito framework and you need an instance of {@link Invocation}, use {@link #createInvocation(Object, MockCreationSettings, Method, RealMethodBehavior, Object...)}.
  *
  * @since 2.10.0
  */
@@ -27,6 +28,11 @@
 public interface InvocationFactory {
 
     /**
+     * @deprecated Use {@link #createInvocation(Object, MockCreationSettings, Method, RealMethodBehavior, Object...)} instead.
+     *
+     * Why deprecated? We found use cases where we need to handle Throwable and ensure correct stack trace filtering
+     * (removing Mockito internals from the stack trace). Hence the introduction of {@link RealMethodBehavior}.
+     *
      * Creates instance of an {@link Invocation} object.
      * This method is useful for framework integrators to programmatically simulate method calls on mocks using {@link MockHandler}.
      * It enables advanced framework integrations.
@@ -40,6 +46,32 @@ public interface InvocationFactory {
      * @return invocation instance
      * @since 2.10.0
      */
-    @Incubating
+    @Deprecated
     Invocation createInvocation(Object target, MockCreationSettings settings, Method method, Callable realMethod, Object... args);
+
+    /**
+     * Behavior of the real method.
+     *
+     * @since 2.14.0
+     */
+    interface RealMethodBehavior<R> extends Serializable {
+        R call() throws Throwable;
+    }
+
+    /**
+     * Creates instance of an {@link Invocation} object.
+     * This method is useful for framework integrators to programmatically simulate method calls on mocks using {@link MockHandler}.
+     * It enables advanced framework integrations.
+     *
+     * @param target the mock object the method is invoked on.
+     * @param settings creation settings of the mock object.
+     * @param method java method invoked on mock.
+     * @param realMethod real method behavior. Needed for spying / invoking real behavior on mock objects.
+     * @param args the java method arguments
+     *
+     * @return invocation instance
+     * @since 2.14.0
+     */
+    @Incubating
+    Invocation createInvocation(Object target, MockCreationSettings settings, Method method, RealMethodBehavior realMethod, Object... args);
 }
diff --git a/version.properties b/version.properties
index c274109d06..e406ba6bbb 100644
--- a/version.properties
+++ b/version.properties
@@ -1,5 +1,5 @@
 #Currently building Mockito version
-version=2.13.4
+version=2.14.0
 
 #Previous version used to generate release notes delta
 previousVersion=2.13.3


Test patch:
diff --git a/src/test/java/org/mockito/InvocationFactoryTest.java b/src/test/java/org/mockito/InvocationFactoryTest.java
new file mode 100644
index 0000000000..893f95f465
--- /dev/null
+++ b/src/test/java/org/mockito/InvocationFactoryTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2018 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+
+package org.mockito;
+
+import org.junit.Test;
+import org.mockito.invocation.Invocation;
+import org.mockito.invocation.InvocationFactory;
+import org.mockitoutil.TestBase;
+
+import java.util.concurrent.Callable;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.withSettings;
+
+public class InvocationFactoryTest extends TestBase {
+    static class TestClass {
+        public String testMethod() throws Throwable {
+            return "un-mocked";
+        }
+    }
+
+    final TestClass mock = spy(TestClass.class);
+
+    @Test
+    public void call_method_that_throws_a_throwable() throws Throwable {
+        Invocation invocation = Mockito.framework().getInvocationFactory().createInvocation(mock,
+            withSettings().build(TestClass.class),
+            TestClass.class.getDeclaredMethod("testMethod"),
+            new InvocationFactory.RealMethodBehavior() {
+            @Override
+            public Object call() throws Throwable {
+                throw new Throwable("mocked");
+            }
+        });
+
+        try {
+            Mockito.mockingDetails(mock).getMockHandler().handle(invocation);
+        } catch (Throwable t) {
+            assertEquals("mocked", t.getMessage());
+            return;
+        }
+
+        fail();
+    }
+
+    @Test
+    public void call_method_that_returns_a_string() throws Throwable {
+        Invocation invocation = Mockito.framework().getInvocationFactory().createInvocation(mock,
+            withSettings().build(TestClass.class),
+            TestClass.class.getDeclaredMethod("testMethod"),
+            new InvocationFactory.RealMethodBehavior() {
+                @Override
+                public Object call() throws Throwable {
+                    return "mocked";
+                }
+            });
+
+        Object ret = Mockito.mockingDetails(mock).getMockHandler().handle(invocation);
+        assertEquals("mocked", ret);
+    }
+
+    @Test
+    public void deprecated_api_still_works() throws Throwable {
+        Invocation invocation = Mockito.framework().getInvocationFactory().createInvocation(mock,
+            withSettings().build(TestClass.class),
+            TestClass.class.getDeclaredMethod("testMethod"),
+            new Callable() {
+                public Object call() throws Exception {
+                    return "mocked";
+                }
+            });
+
+        Object ret = Mockito.mockingDetails(mock).getMockHandler().handle(invocation);
+        assertEquals("mocked", ret);
+    }
+}
diff --git a/src/test/java/org/mockito/StaticMockingExperimentTest.java b/src/test/java/org/mockito/StaticMockingExperimentTest.java
index a74d5ebd8d..bb5e22573e 100644
--- a/src/test/java/org/mockito/StaticMockingExperimentTest.java
+++ b/src/test/java/org/mockito/StaticMockingExperimentTest.java
@@ -10,12 +10,12 @@
 import org.mockito.exceptions.verification.WantedButNotInvoked;
 import org.mockito.exceptions.verification.junit.ArgumentsAreDifferent;
 import org.mockito.invocation.Invocation;
+import org.mockito.invocation.InvocationFactory;
 import org.mockito.invocation.MockHandler;
 import org.mockitoutil.TestBase;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
-import java.util.concurrent.Callable;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -43,9 +43,9 @@ public class StaticMockingExperimentTest extends TestBase {
     Foo mock = Mockito.mock(Foo.class);
     MockHandler handler = Mockito.mockingDetails(mock).getMockHandler();
     Method staticMethod;
-    Callable realMethod = new Callable() {
+    InvocationFactory.RealMethodBehavior realMethod = new InvocationFactory.RealMethodBehavior() {
         @Override
-        public Object call() throws Exception {
+        public Object call() throws Throwable {
             return null;
         }
     };