Repository: mockito/mockito
Issue: Mock static class seems records wrong invocations if called nested method throws exception

If mocking a static class where method calls another static method within same class and inner class is throwing an expception the
the stubbing does not work as expected (instead of returning stubbed answer the execption is thrown)

Class `StaticTest.java`:
```
import java.util.concurrent.atomic.AtomicInteger;

public class StaticClass {
    public static final AtomicInteger countOuter = new AtomicInteger(0);
    public static final AtomicInteger countInner = new AtomicInteger(0);
    public static final AtomicInteger resultInner = new AtomicInteger();

    public static int outerMethod(String aParam1, Integer aParam2, Object aParam3) {
        countOuter.incrementAndGet();
        resultInner.set(innerMethod(aParam1, aParam2));
        return countOuter.incrementAndGet();
    }

    public static int innerMethod(String aParam1, Integer aParam2) {
        throw new NullPointerException();
//        return countInner.incrementAndGet();
    }
}
```

Class `TestStaticMock.java`:
```
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Answers.CALLS_REAL_METHODS;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;

public class TestStaticMock {
    @Test
    void testMock() {
        try (MockedStatic<StaticClass> mocked = mockStatic(StaticClass.class, CALLS_REAL_METHODS)) {
            mocked.when(() -> StaticClass.outerMethod(any(), any(), any())).thenReturn(Integer.valueOf(-1));

            assertEquals(-1, StaticClass.outerMethod("DUMMY", Integer.valueOf(10), Double.valueOf(2.0))); // (1)
//            assertThrows(NullPointerException.class, () -> StaticClass.outerMethod("DUMMY", Integer.valueOf(10), Double.valueOf(2.0)));  // (2)

            mocked.verify(() -> StaticClass.outerMethod("DUMMY", Integer.valueOf(10), Double.valueOf(2.0)));
            mocked.verifyNoMoreInteractions();
        }
    }
}
```

Expcected behaviour is `assertEquals` succeeds because the stubbing returns the expected value but exception raised
by `innerMethod` is returned. If line marked with (1) is comment out and comment for line with (2) is removed the validation
of check for `noMoreInteraction` prints

```
org.mockito.exceptions.verification.NoInteractionsWanted: 
No interactions wanted here:
-> at TestStaticMock.testMock(TestStaticMock.java:20)
But found this interaction on mock 'StaticClass.class':
-> at TestStaticMock.lambda$0(TestStaticMock.java:14)
***
For your reference, here is the list of all invocations ([?] - means unverified).
1. [?]-> at TestStaticMock.lambda$0(TestStaticMock.java:14)
2. -> at TestStaticMock.lambda$1(TestStaticMock.java:17)
3. [?]-> at StaticClass.outerMethod(StaticClass.java:10)

	at TestStaticMock.testMock(TestStaticMock.java:20)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
...
```
showing inner invocation not expected and recording of stubbing.

Issue comments:

Pull request review comments:

Patch that resolved the issue:
diff --git a/src/main/java/org/mockito/internal/handler/MockHandlerImpl.java b/src/main/java/org/mockito/internal/handler/MockHandlerImpl.java
index 17b967efcf..e58659a166 100644
--- a/src/main/java/org/mockito/internal/handler/MockHandlerImpl.java
+++ b/src/main/java/org/mockito/internal/handler/MockHandlerImpl.java
@@ -107,18 +107,25 @@ public Object handle(Invocation invocation) throws Throwable {
                 mockingProgress().reportOngoingStubbing(ongoingStubbing);
             }
         } else {
-            Object ret = mockSettings.getDefaultAnswer().answer(invocation);
-            DefaultAnswerValidator.validateReturnValueFor(invocation, ret);
-
-            // Mockito uses it to redo setting invocation for potential stubbing in case of partial
-            // mocks / spies.
-            // Without it, the real method inside 'when' might have delegated to other self method
-            // and overwrite the intended stubbed method with a different one.
-            // This means we would be stubbing a wrong method.
-            // Typically this would led to runtime exception that validates return type with stubbed
-            // method signature.
-            invocationContainer.resetInvocationForPotentialStubbing(invocationMatcher);
-            return ret;
+            try {
+                Object ret = mockSettings.getDefaultAnswer().answer(invocation);
+                DefaultAnswerValidator.validateReturnValueFor(invocation, ret);
+
+                return ret;
+            } finally {
+                // Mockito uses it to redo setting invocation for potential stubbing in case of
+                // partial
+                // mocks / spies.
+                // Without it, the real method inside 'when' might have delegated to other self
+                // method
+                // and overwrite the intended stubbed method with a different one.
+                // This means we would be stubbing a wrong method.
+                // Typically this would led to runtime exception that validates return type with
+                // stubbed
+                // method signature.
+                invocationContainer.resetInvocationForPotentialStubbing(invocationMatcher);
+                mockingProgress().reportOngoingStubbing(ongoingStubbing);
+            }
         }
     }
 


Test patch:
diff --git a/src/test/java/org/mockitousage/misuse/SpyStubbingMisuseTest.java b/src/test/java/org/mockitousage/spies/SpyAsDefaultMockUsageTest.java
similarity index 61%
rename from src/test/java/org/mockitousage/misuse/SpyStubbingMisuseTest.java
rename to src/test/java/org/mockitousage/spies/SpyAsDefaultMockUsageTest.java
index 9f0acfee4e..463b9370d7 100644
--- a/src/test/java/org/mockitousage/misuse/SpyStubbingMisuseTest.java
+++ b/src/test/java/org/mockitousage/spies/SpyAsDefaultMockUsageTest.java
@@ -2,16 +2,14 @@
  * Copyright (c) 2007 Mockito contributors
  * This program is made available under the terms of the MIT License.
  */
-package org.mockitousage.misuse;
+package org.mockitousage.spies;
 
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertSame;
 import static org.mockito.Mockito.*;
 
 import org.junit.Test;
-import org.mockito.exceptions.misusing.WrongTypeOfReturnValue;
 
-public class SpyStubbingMisuseTest {
+public class SpyAsDefaultMockUsageTest {
 
     @Test
     public void nestedWhenTest() {
@@ -19,15 +17,8 @@ public void nestedWhenTest() {
         Sampler mpoo = mock(Sampler.class);
         Producer out = spy(new Producer(mfoo));
 
-        try {
-            when(out.produce()).thenReturn(mpoo);
-            fail();
-        } catch (WrongTypeOfReturnValue e) {
-            assertThat(e.getMessage())
-                    .contains("spy")
-                    .contains("syntax")
-                    .contains("doReturn|Throw");
-        }
+        when(out.produce()).thenReturn(mpoo);
+        assertSame(mpoo, out.produce());
     }
 
     public class Sample {}
diff --git a/subprojects/inline/src/test/java/org/mockitoinline/bugs/OngoingStubShiftTest.java b/subprojects/inline/src/test/java/org/mockitoinline/bugs/OngoingStubShiftTest.java
new file mode 100644
index 0000000000..47062fedfd
--- /dev/null
+++ b/subprojects/inline/src/test/java/org/mockitoinline/bugs/OngoingStubShiftTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2007 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockitoinline.bugs;
+
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+import org.mockito.MockedStatic;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
+import static org.mockito.Mockito.mockStatic;
+
+public class OngoingStubShiftTest {
+
+    private static class StaticInt {
+        static int getInt() {
+            return 1;
+        }
+    }
+
+    private static class StaticStr {
+        static String getStr() {
+            return Integer.toString(StaticInt.getInt());
+        }
+    }
+
+    @Test
+    public void keep_ongoing_stub_when_spy() {
+        try (MockedStatic<StaticInt> mockInt = mockStatic(StaticInt.class);
+             MockedStatic<StaticStr> mockStr = mockStatic(StaticStr.class, CALLS_REAL_METHODS)) {
+
+            mockStr.when(StaticStr::getStr).thenReturn("1");
+            assertEquals("1", StaticStr.getStr());
+        }
+    }
+
+    private static class StaticWithException {
+        static String getString() {
+            return Integer.toString(getInt());
+        }
+
+        static int getInt() {
+            throw new NullPointerException();
+        }
+    }
+
+    @Test
+    public void keep_ongoing_stub_when_exception() {
+        try (MockedStatic<StaticWithException> mock = mockStatic(StaticWithException.class, CALLS_REAL_METHODS)) {
+            mock.when(StaticWithException::getString).thenReturn("1");
+            assertEquals("1", StaticWithException.getString());
+        }
+    }
+}