Repository: mockito/mockito
Issue: Concise way to collect multiple verify failures, ideally with JUnitCollector or  derivative

If you are using more than one verify statement as test post-conditions, it would be nice to be able to collect each failure and continue with the remaining verify statements or asserts that follow.  JUnit provides the ErrorCollector @Rule to facilitate this kid of thing, but the current [ErrorCollector API](http://junit.org/apidocs/org/junit/rules/ErrorCollector.html) requires either
- a Matcher<T> and a value to compare
- or a Callable<Object>

The Mockito verify statements only return void, however, since they depend on throwing exceptions.  I have not thought of an ideal, non-disruptive way to use JUnit's ErrorCollector to aggregate multiple Mockito verify failure


Issue comments:
As a possible example of the way to handle it, maybe it could be modeled after the same way that AssertJ added deferred composite assertion handling through [their own JUnit @Rule](http://joel-costigliola.github.io/assertj/assertj-core-features-highlight.html#soft-assertions):


Pull request review comments:
I think this rule should be instantiated this way (Also the method should only expose an interface for example `VerificationCollector`) :

``` java
@Rule VerificationCollector collector = MockitoJUnit.collector();
```

Since it is JUnit specific, this whole PR should live within the package `org.mockito.junit`, we'd like to move runners as well under this package too.

Don't do these hacks ;)
Instead play with core JUnit functionality like `JUnitCore`, there's some example in the test code base (like `org.mockitousage.bugs.MockitoRunnerBreaksWhenNoTestMethodsTest`)

I honestly didn't have a clue how to fix this and internet wasn't helpful either. I will check out the test you mentioned!

No problem ;)

I don't think this class needs to be public anymore ;)


Patch that resolved the issue:
diff --git a/src/main/java/org/mockito/junit/MockitoJUnit.java b/src/main/java/org/mockito/junit/MockitoJUnit.java
index f13b667796..b680079e80 100644
--- a/src/main/java/org/mockito/junit/MockitoJUnit.java
+++ b/src/main/java/org/mockito/junit/MockitoJUnit.java
@@ -16,4 +16,14 @@ public class MockitoJUnit {
     public static MockitoRule rule() {
         return new MockitoJUnitRule();
     }
+
+    /**
+     * Creates a rule instance that can perform lazy verifications.
+     *
+     * @see VerificationCollector
+     * @return the rule instance
+     */
+    public static VerificationCollector collector() {
+        return new VerificationCollectorImpl();
+    }
 }
diff --git a/src/main/java/org/mockito/junit/VerificationCollector.java b/src/main/java/org/mockito/junit/VerificationCollector.java
new file mode 100644
index 0000000000..432dc55b36
--- /dev/null
+++ b/src/main/java/org/mockito/junit/VerificationCollector.java
@@ -0,0 +1,62 @@
+package org.mockito.junit;
+
+import org.junit.rules.TestRule;
+import org.mockito.exceptions.base.MockitoAssertionError;
+import org.mockito.verification.VerificationMode;
+
+/**
+ * Use this rule in order to collect multiple verification failures and report at once.
+ *
+ * In the example below, the verification failure thrown by {@code byteReturningMethod()} does not block
+ * verifying against the {@code simpleMethod()}. After the test is run, a report is generated stating all
+ * collect verification failures.
+ *
+ * <pre class="code"><code class="java">
+ *   &#064;Rule
+ *   public VerificationCollector collector = MockitoJUnit.collector();
+ *
+ *   &#064;Test
+ *   public void should_fail() {
+ *       IMethods methods = mock(IMethods.class);
+ *
+ *       collector.verify(methods).byteReturningMethod();
+ *       collector.verify(methods).simpleMethod();
+ *   }
+ * </code></pre>
+ *
+ * @see org.mockito.Mockito#verify(Object)
+ * @see org.mockito.Mockito#verify(Object, VerificationMode)
+ */
+public interface VerificationCollector extends TestRule {
+
+    /**
+     * Lazily verify certain behaviour happened once.
+     *
+     * @see org.mockito.Mockito#verify(Object)
+     *
+     * @param <T> The type of the mock
+     * @param mock to be verified
+     * @return mock object itself
+     */
+    <T> T verify(T mock);
+
+    /**
+     * Lazily verify certain behaviour happened at least once / exact number of times / never.
+     *
+     * @see org.mockito.Mockito#verify(Object, VerificationMode)
+     *
+     * @param mock to be verified
+     * @param mode times(x), atLeastOnce() or never()
+     * @param <T> The type of the mock
+     * @return mock object itself
+     */
+    <T> T verify(T mock, VerificationMode mode);
+
+    /**
+     * Collect all lazily verified behaviour. If there were failed verifications, it will
+     * throw a MockitoAssertionError containing all messages indicating the failed verifications.
+     *
+     * @throws MockitoAssertionError If there were failed verifications
+     */
+    void collectAndReport() throws MockitoAssertionError;
+}
diff --git a/src/main/java/org/mockito/junit/VerificationCollectorImpl.java b/src/main/java/org/mockito/junit/VerificationCollectorImpl.java
new file mode 100644
index 0000000000..64b7ec2697
--- /dev/null
+++ b/src/main/java/org/mockito/junit/VerificationCollectorImpl.java
@@ -0,0 +1,89 @@
+package org.mockito.junit;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.mockito.exceptions.base.MockitoAssertionError;
+import org.mockito.internal.MockitoCore;
+import org.mockito.internal.verification.api.VerificationData;
+import org.mockito.verification.VerificationMode;
+
+import static org.mockito.Mockito.times;
+
+/**
+ * Mockito implementation of VerificationCollector.
+ */
+class VerificationCollectorImpl implements VerificationCollector {
+
+    private static final MockitoCore MOCKITO_CORE = new MockitoCore();
+
+    private StringBuilder builder;
+    private int numberOfFailures;
+
+    public VerificationCollectorImpl() {
+        this.resetBuilder();
+    }
+
+    public Statement apply(final Statement base, final Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                base.evaluate();
+                VerificationCollectorImpl.this.collectAndReport();
+            }
+        };
+    }
+
+    public <T> T verify(T mock) {
+        return MOCKITO_CORE.verify(mock, new VerificationWrapper(times(1)));
+    }
+
+    public <T> T verify(T mock, VerificationMode mode) {
+        return MOCKITO_CORE.verify(mock, new VerificationWrapper(mode));
+    }
+
+    public void collectAndReport() throws MockitoAssertionError {
+        if (this.numberOfFailures > 0) {
+            String error = this.builder.toString();
+
+            this.resetBuilder();
+
+            throw new MockitoAssertionError(error);
+        }
+    }
+
+    private void resetBuilder() {
+        this.builder = new StringBuilder()
+                .append("There were multiple verification failures:");
+        this.numberOfFailures = 0;
+    }
+
+    private void append(String message) {
+        this.numberOfFailures++;
+        this.builder.append('\n')
+                .append(this.numberOfFailures).append(". ")
+                .append(message.substring(1, message.length()));
+    }
+
+    private class VerificationWrapper implements VerificationMode {
+
+        private final VerificationMode delegate;
+
+        private VerificationWrapper(VerificationMode delegate) {
+            this.delegate = delegate;
+        }
+
+        public void verify(VerificationData data) {
+            try {
+                this.delegate.verify(data);
+            } catch (MockitoAssertionError error) {
+                VerificationCollectorImpl.this.append(error.getMessage());
+            }
+        }
+
+        public VerificationMode description(String description) {
+            throw new IllegalStateException("Should not fail in this mode");
+        }
+    }
+
+}


Test patch:
diff --git a/src/test/java/org/mockitousage/junitrule/VerificationCollectorImplTest.java b/src/test/java/org/mockitousage/junitrule/VerificationCollectorImplTest.java
new file mode 100644
index 0000000000..4749304fee
--- /dev/null
+++ b/src/test/java/org/mockitousage/junitrule/VerificationCollectorImplTest.java
@@ -0,0 +1,121 @@
+package org.mockitousage.junitrule;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.JUnitCore;
+import org.junit.runner.Result;
+import org.mockito.exceptions.base.MockitoAssertionError;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.VerificationCollector;
+import org.mockitousage.IMethods;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+
+public class VerificationCollectorImplTest {
+
+    @Test
+    public void should_not_throw_any_exceptions_when_verifications_are_succesful() {
+        VerificationCollector collector = MockitoJUnit.collector();
+
+        IMethods methods = mock(IMethods.class);
+        methods.simpleMethod();
+
+        collector.verify(methods).simpleMethod();
+        collector.collectAndReport();
+    }
+
+    @Test(expected = MockitoAssertionError.class)
+    public void should_collect_verification_failures() {
+        VerificationCollector collector = MockitoJUnit.collector();
+
+        IMethods methods = mock(IMethods.class);
+
+        collector.verify(methods).simpleMethod();
+        collector.collectAndReport();
+    }
+
+    @Test
+    public void should_collect_multiple_verification_failures() {
+        VerificationCollector collector = MockitoJUnit.collector();
+
+        IMethods methods = mock(IMethods.class);
+
+        collector.verify(methods).simpleMethod();
+        collector.verify(methods).byteReturningMethod();
+        try {
+            collector.collectAndReport();
+            fail();
+        } catch (MockitoAssertionError error) {
+            assertThat(error).hasMessageContaining("1. Wanted but not invoked:");
+            assertThat(error).hasMessageContaining("2. Wanted but not invoked:");
+        }
+    }
+
+    @Test
+    public void should_only_collect_failures_ignore_succesful_verifications() {
+        VerificationCollector collector = MockitoJUnit.collector();
+
+        IMethods methods = mock(IMethods.class);
+
+        collector.verify(methods, never()).simpleMethod();
+        collector.verify(methods).byteReturningMethod();
+
+        this.assertAtLeastOneFailure(collector);
+    }
+
+    @Test
+    public void should_continue_collecting_after_failing_verification() {
+        VerificationCollector collector = MockitoJUnit.collector();
+
+        IMethods methods = mock(IMethods.class);
+        methods.simpleMethod();
+
+        collector.verify(methods).byteReturningMethod();
+        collector.verify(methods).simpleMethod();
+
+        this.assertAtLeastOneFailure(collector);
+    }
+
+    private void assertAtLeastOneFailure(VerificationCollector collector) {
+        try {
+            collector.collectAndReport();
+            fail();
+        } catch (MockitoAssertionError error) {
+            assertThat(error).hasMessageContaining("1. Wanted but not invoked:");
+            assertThat(error.getMessage()).doesNotContain("2.");
+        }
+    }
+
+    @Test
+    public void should_invoke_collector_rule_after_test() {
+        JUnitCore runner = new JUnitCore();
+        Result result = runner.run(VerificationCollectorRuleInner.class);
+
+        assertThat(result.getFailureCount()).isEqualTo(1);
+        assertThat(result.getFailures().get(0).getMessage()).contains("1. Wanted but not invoked:");
+    }
+
+    public static class VerificationCollectorRuleInner {
+
+        @Rule
+        public VerificationCollector collector = MockitoJUnit.collector();
+
+        @Test
+        public void should_fail() {
+            IMethods methods = mock(IMethods.class);
+
+            collector.verify(methods).simpleMethod();
+        }
+
+        @Test
+        public void should_not_fail() {
+            IMethods methods = mock(IMethods.class);
+            methods.simpleMethod();
+
+            collector.verify(methods).simpleMethod();
+        }
+    }
+}