Repository: mockito/mockito
Issue: Feature request: Method to reset mock interactions/invocations without resetting stubs
Labels: enhancement

I'm still missing the ability to reset the invocations on a mock, while keeping all the stubbing, so this is a GitHub equivalent of https://code.google.com/p/mockito/issues/detail?id=316

Some background discussion exists in a [thread](https://groups.google.com/forum/#!topic/mockito/5G-XnpIhP3s) that also refers to [issue 84](https://code.google.com/p/mockito/issues/detail?id=84) for which there already [is a GitHub issue](https://github.com/mockito/mockito/issues/86).


Issue comments:
Hi, in your use case could you be interested to stubbing only i.e. not interested by verifications ?

In this case there's this setting may prove useful : `withSettings().stubOnly()`

No, that would not solve my problem.

My current issue is that I have a large number of permutations (x \* y) of input data, and the plan was to create reusable methods for one of the "dimensions" (`x_case1()`, `x_case2()`, `x_case3()`, ...) and then from each test case of the other dimensions (`@Test y_case1()`) call the methods of the first dimension **and pass a mock as a parameter**. That way I can set up the stubbing once, for the Y dimension, and then test and `verify()` the different X scenarios with that stub.

The workaround in this case could be to pass a lambda that does the stubbing, after each scenario has created it's mock.

(I do agree it would be preferable in this particular case to refactor this to something that is more testable, and hopefully I can get part of the way there, but sometimes reality is just complex and it is definitely not the first time I've missed this particular feature in Mockito.)

OK

Well at the moment the team is clearly busy in other RL projects. If you manage to make a nice PR (see the contributing guide) of this feature before we do work on it, which seems most probable we may integrate your work :)

I created a new issue (#284) for removing any invocations which have been verified. In my optioning resetting the invocations would then not been needed anymore and the current reset can stay as-is. This also give you a stronger guarantee that you're really verifying all invocations of the mock.


Pull request review comments:
I like the notion of _clearing_, I propose the pull this word up to the `Mockito` class facade. instead of using 3 different words (reset, clear, remove).


Patch that resolved the issue:
diff --git a/src/main/java/org/mockito/Mockito.java b/src/main/java/org/mockito/Mockito.java
index 25db83abee..302bd6cce6 100644
--- a/src/main/java/org/mockito/Mockito.java
+++ b/src/main/java/org/mockito/Mockito.java
@@ -1703,6 +1703,21 @@ public static <T> void reset(T ... mocks) {
         MOCKITO_CORE.reset(mocks);
     }
 
+    /**
+     * Use this method in order to only clear invocations, when stubbing is non-trivial. Use-cases can be:
+     * <ul>
+     *     <li>You are using a dependency injection framework to inject your mocks.</li>
+     *     <li>The mock is used in a stateful scenario. For example a class is Singleton which depends on your mock.</li>
+     * </ul>
+     *
+     * <b>Try to avoid this method at all costs. Only clear invocations if you are unable to efficiently test your program.</b>
+     * @param <T> The type of the mocks
+     * @param mocks The mocks to clear the invocations for
+     */
+    public static <T> void clearInvocations(T ... mocks) {
+        MOCKITO_CORE.clearInvocations(mocks);
+    }
+
     /**
      * Checks if any of given mocks has any unverified interaction.
      * <p>
diff --git a/src/main/java/org/mockito/internal/MockitoCore.java b/src/main/java/org/mockito/internal/MockitoCore.java
index 8ad222f849..e6dcb469bc 100644
--- a/src/main/java/org/mockito/internal/MockitoCore.java
+++ b/src/main/java/org/mockito/internal/MockitoCore.java
@@ -95,6 +95,16 @@ public <T> void reset(T ... mocks) {
             mockUtil.resetMock(m);
         }
     }
+
+    public <T> void clearInvocations(T ... mocks) {
+        mockingProgress.validateState();
+        mockingProgress.reset();
+        mockingProgress.resetOngoingStubbing();
+
+        for (T m : mocks) {
+            mockUtil.getMockHandler(m).getInvocationContainer().clearInvocations();
+        }
+    }
     
     public void verifyNoMoreInteractions(Object... mocks) {
         assertMocksNotEmpty(mocks);
@@ -182,4 +192,5 @@ public Object[] ignoreStubs(Object... mocks) {
     public MockingDetails mockingDetails(Object toInspect) {
         return new DefaultMockingDetails(toInspect, new MockUtil());
     }
+
 }
diff --git a/src/main/java/org/mockito/internal/stubbing/InvocationContainer.java b/src/main/java/org/mockito/internal/stubbing/InvocationContainer.java
index 0e82e85cb9..b926183f63 100644
--- a/src/main/java/org/mockito/internal/stubbing/InvocationContainer.java
+++ b/src/main/java/org/mockito/internal/stubbing/InvocationContainer.java
@@ -12,5 +12,7 @@
 public interface InvocationContainer {
     List<Invocation> getInvocations();
 
+    void clearInvocations();
+
     List<StubbedInvocationMatcher> getStubbedInvocations();
 }
diff --git a/src/main/java/org/mockito/internal/stubbing/InvocationContainerImpl.java b/src/main/java/org/mockito/internal/stubbing/InvocationContainerImpl.java
index 9d10ab9bce..fb391e3cef 100644
--- a/src/main/java/org/mockito/internal/stubbing/InvocationContainerImpl.java
+++ b/src/main/java/org/mockito/internal/stubbing/InvocationContainerImpl.java
@@ -121,6 +121,10 @@ public List<Invocation> getInvocations() {
         return registeredInvocations.getAll();
     }
 
+    public void clearInvocations() {
+        registeredInvocations.clear();
+    }
+
     public List<StubbedInvocationMatcher> getStubbedInvocations() {
         return stubbed;
     }
diff --git a/src/main/java/org/mockito/internal/verification/DefaultRegisteredInvocations.java b/src/main/java/org/mockito/internal/verification/DefaultRegisteredInvocations.java
index a9690066a2..7b6442bdd2 100644
--- a/src/main/java/org/mockito/internal/verification/DefaultRegisteredInvocations.java
+++ b/src/main/java/org/mockito/internal/verification/DefaultRegisteredInvocations.java
@@ -44,6 +44,12 @@ public List<Invocation> getAll() {
         return ListUtil.filter(copiedList, new RemoveToString());
     }
 
+    public void clear() {
+        synchronized (invocations) {
+            invocations.clear();
+        }
+    }
+
     public boolean isEmpty() {
         synchronized (invocations) {
             return invocations.isEmpty();
diff --git a/src/main/java/org/mockito/internal/verification/RegisteredInvocations.java b/src/main/java/org/mockito/internal/verification/RegisteredInvocations.java
index 1cb2131775..75c0b3f224 100644
--- a/src/main/java/org/mockito/internal/verification/RegisteredInvocations.java
+++ b/src/main/java/org/mockito/internal/verification/RegisteredInvocations.java
@@ -21,6 +21,8 @@ public interface RegisteredInvocations {
 
     List<Invocation> getAll();
 
+    void clear();
+
     boolean isEmpty();
 
 }
diff --git a/src/main/java/org/mockito/internal/verification/SingleRegisteredInvocation.java b/src/main/java/org/mockito/internal/verification/SingleRegisteredInvocation.java
index 07da74c378..67ceafa52e 100644
--- a/src/main/java/org/mockito/internal/verification/SingleRegisteredInvocation.java
+++ b/src/main/java/org/mockito/internal/verification/SingleRegisteredInvocation.java
@@ -27,6 +27,10 @@ public List<Invocation> getAll() {
         return Collections.emptyList();
     }
 
+    public void clear() {
+        invocation = null;
+    }
+
     public boolean isEmpty() {
         return invocation == null;
     }


Test patch:
diff --git a/src/test/java/org/mockitousage/basicapi/ResetInvocationsTest.java b/src/test/java/org/mockitousage/basicapi/ResetInvocationsTest.java
new file mode 100644
index 0000000000..3f18478086
--- /dev/null
+++ b/src/test/java/org/mockitousage/basicapi/ResetInvocationsTest.java
@@ -0,0 +1,48 @@
+package org.mockitousage.basicapi;
+
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.exceptions.misusing.NotAMockException;
+import org.mockitousage.IMethods;
+import org.mockitoutil.TestBase;
+
+import static org.mockito.Mockito.*;
+
+public class ResetInvocationsTest extends TestBase {
+
+    @Mock IMethods methods;
+    @Mock IMethods moarMethods;
+
+    @Test
+    public void reset_invocations_should_reset_only_invocations() {
+        when(methods.simpleMethod()).thenReturn("return");
+
+        methods.simpleMethod();
+        verify(methods).simpleMethod();
+
+        clearInvocations(methods);
+
+        verifyNoMoreInteractions(methods);
+        assertEquals("return", methods.simpleMethod());
+    }
+
+    @Test
+    public void should_reset_invocations_on_multiple_mocks() {
+        methods.simpleMethod();
+        moarMethods.simpleMethod();
+
+        clearInvocations(methods, moarMethods);
+
+        verifyNoMoreInteractions(methods, moarMethods);
+    }
+
+    @Test(expected = NotAMockException.class)
+    public void resettingNonMockIsSafe() {
+        clearInvocations("");
+    }
+
+    @Test(expected = NotAMockException.class)
+    public void resettingNullIsSafe() {
+        clearInvocations(new Object[]{null});
+    }
+}