Repository: mockito/mockito
Issue: Some VerificationModes don't have a toString implementation.

Some user-facing VerificationModes don't have a `toString()` implementation. This includes:

- `AtMost`
- `Only`
- `After`
- `Timeout`

I ran into this issue while using Mockito with a proprietary [Jasmine](https://jasmine.github.io/)-like library for JUnit. Here is a short example written with JUnit and the open-source runner [Oleaster](https://github.com/mscharhag/oleaster):

```
@RunWith(OleasterRunner.class)
public class DemoTest {

    private MyClass myClass;

    {
        describe("demo", () -> {

            beforeEach(() -> {
                myClass = Mockito.mock(MyClass.class);
                myClass.myMethod();
            });

            verifyMyMethodCalled(atLeast(1));
            verifyMyMethodCalled(atMost(1));
            verifyMyMethodCalled(only());
            verifyMyMethodCalled(after(1));
        });
    }

    private void verifyMyMethodCalled(VerificationMode verificationMode) {
        it(String.format("should call myMethod. %s", verificationMode), () -> {
            verify(myClass, verificationMode).myMethod();
        });
    }
}
```

Extracting the assertion and the test-naming part to a specific method (`verifyMyMethodCalled(VerificationMode verificationMode)`) saves a lot of boilerplate and helps with maintainability, especially since a real-life test class will most likely be longer than this example. However, the following screenshot shows why the last 3 verificationModes aren't so helpful without a proper `toString()` method:

![image](https://user-images.githubusercontent.com/22927506/131226758-72a58f27-c1fb-44e2-8db2-70ab5476c7f5.png)

I fixed this, in my use case, this by extending the offending `VerificationMode` in a custom factory method:
```
private static VerificationMode atMost(int times) {
    return new AtMost(times) {
        @Override
        public void verify(VerificationData data) {
            super.verify(data);
        }

        @Override
        public String toString() {
            return "Wanted invocations count: at most " + times;
        }
    };
}
```
This quickly becomes cumbersome however, if you want to do it for multiple `VerificationMode`s or in multiple projects. Since most user-facing `VerificationMode`s already have a `toString()` method (e.g. `atLeast` in my example), the missing `toString()` methods could be included in Mockito.

I have a PR that is almost ready for this, and would gladly submit it.

EDIT: added `Timeout` to the list.

Issue comments:
> I have a PR that is almost ready for this, and would gladly submit it.

Yes that seems good to me, looking forward to it!

Pull request review comments:

Patch that resolved the issue:
diff --git a/src/main/java/org/mockito/internal/verification/AtMost.java b/src/main/java/org/mockito/internal/verification/AtMost.java
index 57dedc4c66..72c0d00275 100644
--- a/src/main/java/org/mockito/internal/verification/AtMost.java
+++ b/src/main/java/org/mockito/internal/verification/AtMost.java
@@ -51,4 +51,9 @@ private void removeAlreadyVerified(List<Invocation> invocations) {
             }
         }
     }
+
+    @Override
+    public String toString() {
+        return "Wanted invocations count: at most " + maxNumberOfInvocations;
+    }
 }
diff --git a/src/main/java/org/mockito/internal/verification/Only.java b/src/main/java/org/mockito/internal/verification/Only.java
index 64ec47d5ab..aea8981d10 100644
--- a/src/main/java/org/mockito/internal/verification/Only.java
+++ b/src/main/java/org/mockito/internal/verification/Only.java
@@ -34,4 +34,9 @@ public void verify(VerificationData data) {
         }
         markVerified(chunk.get(0), target);
     }
+
+    @Override
+    public String toString() {
+        return "Wanted invocations count: 1 and no other method invoked";
+    }
 }
diff --git a/src/main/java/org/mockito/verification/After.java b/src/main/java/org/mockito/verification/After.java
index 5e7a0b8eae..ed87333925 100644
--- a/src/main/java/org/mockito/verification/After.java
+++ b/src/main/java/org/mockito/verification/After.java
@@ -38,4 +38,13 @@ public After(long delayMillis, VerificationMode verificationMode) {
     protected VerificationMode copySelfWithNewVerificationMode(VerificationMode verificationMode) {
         return new After(wrappedVerification.copyWithVerificationMode(verificationMode));
     }
+
+    @Override
+    public String toString() {
+        return "Wanted after "
+                + wrappedVerification.getTimer().duration()
+                + " ms: ["
+                + wrappedVerification.getDelegate()
+                + "]";
+    }
 }
diff --git a/src/main/java/org/mockito/verification/Timeout.java b/src/main/java/org/mockito/verification/Timeout.java
index 502bb9fc0e..405a4356d8 100644
--- a/src/main/java/org/mockito/verification/Timeout.java
+++ b/src/main/java/org/mockito/verification/Timeout.java
@@ -62,4 +62,13 @@ public VerificationMode atMost(int maxNumberOfInvocations) {
     public VerificationMode never() {
         throw atMostAndNeverShouldNotBeUsedWithTimeout();
     }
+
+    @Override
+    public String toString() {
+        return "Wanted after at most "
+                + wrappedVerification.getTimer().duration()
+                + " ms: ["
+                + wrappedVerification.getDelegate()
+                + "]";
+    }
 }


Test patch:
diff --git a/src/test/java/org/mockito/internal/verification/DummyVerificationMode.java b/src/test/java/org/mockito/internal/verification/DummyVerificationMode.java
index 92b3b640d4..6b5f4c028f 100644
--- a/src/test/java/org/mockito/internal/verification/DummyVerificationMode.java
+++ b/src/test/java/org/mockito/internal/verification/DummyVerificationMode.java
@@ -13,4 +13,9 @@ public void verify(VerificationData data) {}
     public VerificationMode description(String description) {
         return new DummyVerificationMode();
     }
+
+    @Override
+    public String toString() {
+        return "Dummy verification mode";
+    }
 }
diff --git a/src/test/java/org/mockitousage/verification/AtMostXVerificationTest.java b/src/test/java/org/mockitousage/verification/AtMostXVerificationTest.java
index 47c78f72ec..f6c08ac8bd 100644
--- a/src/test/java/org/mockitousage/verification/AtMostXVerificationTest.java
+++ b/src/test/java/org/mockitousage/verification/AtMostXVerificationTest.java
@@ -22,6 +22,7 @@
 import org.mockito.exceptions.base.MockitoException;
 import org.mockito.exceptions.verification.MoreThanAllowedActualInvocations;
 import org.mockito.exceptions.verification.NoInteractionsWanted;
+import org.mockito.verification.VerificationMode;
 import org.mockitoutil.TestBase;
 
 public class AtMostXVerificationTest extends TestBase {
@@ -115,6 +116,13 @@ public void shouldDetectUnverifiedInMarkInteractionsAsVerified() throws Exceptio
         }
     }
 
+    @Test
+    public void should_return_formatted_output_from_toString_method() {
+        VerificationMode atMost = atMost(3);
+
+        assertThat(atMost).hasToString("Wanted invocations count: at most 3");
+    }
+
     private void undesiredInteraction() {
         mock.add("");
     }
diff --git a/src/test/java/org/mockitousage/verification/OnlyVerificationTest.java b/src/test/java/org/mockitousage/verification/OnlyVerificationTest.java
index 02015e0e2a..64e5a6fb9c 100644
--- a/src/test/java/org/mockitousage/verification/OnlyVerificationTest.java
+++ b/src/test/java/org/mockitousage/verification/OnlyVerificationTest.java
@@ -4,6 +4,7 @@
  */
 package org.mockitousage.verification;
 
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.only;
@@ -15,6 +16,7 @@
 import org.mockito.Mock;
 import org.mockito.exceptions.verification.NoInteractionsWanted;
 import org.mockito.exceptions.verification.WantedButNotInvoked;
+import org.mockito.verification.VerificationMode;
 import org.mockitoutil.TestBase;
 
 public class OnlyVerificationTest extends TestBase {
@@ -85,4 +87,11 @@ public void shouldVerifyMethodWasInvokedExclusivelyWhenTwoMocksInUse() {
         verify(mock, only()).clear();
         verify(mock2, only()).get(0);
     }
+
+    @Test
+    public void should_return_formatted_output_from_toString_method() {
+        VerificationMode only = only();
+
+        assertThat(only).hasToString("Wanted invocations count: 1 and no other method invoked");
+    }
 }
diff --git a/src/test/java/org/mockitousage/verification/VerificationWithAfterTest.java b/src/test/java/org/mockitousage/verification/VerificationWithAfterTest.java
index 4fb65530b1..0d3f40be44 100644
--- a/src/test/java/org/mockitousage/verification/VerificationWithAfterTest.java
+++ b/src/test/java/org/mockitousage/verification/VerificationWithAfterTest.java
@@ -6,6 +6,7 @@
 
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.mockito.Mockito.after;
 import static org.mockito.Mockito.verify;
@@ -22,7 +23,9 @@
 import org.mockito.exceptions.verification.MoreThanAllowedActualInvocations;
 import org.mockito.exceptions.verification.NoInteractionsWanted;
 import org.mockito.exceptions.verification.TooManyActualInvocations;
+import org.mockito.internal.verification.DummyVerificationMode;
 import org.mockito.junit.MockitoRule;
+import org.mockito.verification.VerificationMode;
 import org.mockitousage.IMethods;
 import org.mockitoutil.Stopwatch;
 import org.mockitoutil.async.AsyncTesting;
@@ -301,4 +304,29 @@ public void call() {
         // using generous number to avoid timing issues
         watch.assertElapsedTimeIsLessThan(2000, MILLISECONDS);
     }
+
+    @Test
+    public void should_return_formatted_output_from_toString_when_created_with_factory_method() {
+        VerificationMode after = after(3);
+
+        assertThat(after).hasToString("Wanted after 3 ms: [Wanted invocations count: 1]");
+    }
+
+    @Test
+    public void should_return_formatted_output_from_toString_using_wrapped_verification_mode() {
+        org.mockito.verification.After after =
+                new org.mockito.verification.After(10, new DummyVerificationMode());
+
+        assertThat(after).hasToString("Wanted after 10 ms: [Dummy verification mode]");
+    }
+
+    @Test
+    public void
+            should_return_formatted_output_from_toString_when_chaining_other_verification_mode() {
+        VerificationMode afterAndOnly = after(5).only();
+
+        assertThat(afterAndOnly)
+                .hasToString(
+                        "Wanted after 5 ms: [Wanted invocations count: 1 and no other method invoked]");
+    }
 }
diff --git a/src/test/java/org/mockitousage/verification/VerificationWithTimeoutTest.java b/src/test/java/org/mockitousage/verification/VerificationWithTimeoutTest.java
index ee876e49f8..b51aafd980 100644
--- a/src/test/java/org/mockitousage/verification/VerificationWithTimeoutTest.java
+++ b/src/test/java/org/mockitousage/verification/VerificationWithTimeoutTest.java
@@ -4,6 +4,7 @@
  */
 package org.mockitousage.verification;
 
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.after;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
@@ -21,7 +22,10 @@
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.exceptions.verification.TooFewActualInvocations;
+import org.mockito.internal.verification.DummyVerificationMode;
 import org.mockito.junit.MockitoRule;
+import org.mockito.verification.Timeout;
+import org.mockito.verification.VerificationMode;
 import org.mockitousage.IMethods;
 import org.mockitoutil.Stopwatch;
 import org.mockitoutil.async.AsyncTesting;
@@ -172,6 +176,31 @@ public void should_verify_with_only() {
         verify(mock, timeout(100).only()).oneArg('c');
     }
 
+    @Test
+    public void should_return_formatted_output_from_toString_when_created_with_factory_method() {
+        VerificationMode timeout = timeout(7);
+
+        assertThat(timeout).hasToString("Wanted after at most 7 ms: [Wanted invocations count: 1]");
+    }
+
+    @Test
+    public void should_return_formatted_output_from_toString_using_wrapped_verification_mode() {
+        VerificationMode timeoutAndAtLeastOnce = new Timeout(9, new DummyVerificationMode());
+
+        assertThat(timeoutAndAtLeastOnce)
+                .hasToString("Wanted after at most 9 ms: [Dummy verification mode]");
+    }
+
+    @Test
+    public void
+            should_return_formatted_output_from_toString_when_chaining_other_verification_mode() {
+        VerificationMode timeoutAndOnly = timeout(7).only();
+
+        assertThat(timeoutAndOnly)
+                .hasToString(
+                        "Wanted after at most 7 ms: [Wanted invocations count: 1 and no other method invoked]");
+    }
+
     @Test
     @Ignore("not testable, probably timeout().only() does not make sense")
     public void should_verify_with_only_and_fail() {