diff --git a/src/test/java/org/junit/rules/ErrorCollectorTest.java b/src/test/java/org/junit/rules/ErrorCollectorTest.java
index 25a4542ae37a..672aa2f41d3f 100644
--- a/src/test/java/org/junit/rules/ErrorCollectorTest.java
+++ b/src/test/java/org/junit/rules/ErrorCollectorTest.java
@@ -10,6 +10,7 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
+import org.junit.testsupport.EventCollector;
 
 import java.util.concurrent.Callable;
 
@@ -20,7 +21,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
-import static org.junit.rules.EventCollector.*;
+import static org.junit.testsupport.EventCollectorMatchers.*;
 
 @RunWith(Parameterized.class)
 public class ErrorCollectorTest {
diff --git a/src/test/java/org/junit/rules/EventCollector.java b/src/test/java/org/junit/rules/EventCollector.java
deleted file mode 100644
index 32c872ee8109..000000000000
--- a/src/test/java/org/junit/rules/EventCollector.java
+++ /dev/null
@@ -1,191 +0,0 @@
-package org.junit.rules;
-
-import static org.hamcrest.CoreMatchers.allOf;
-import static org.hamcrest.core.IsEqual.equalTo;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.hamcrest.Matcher;
-import org.hamcrest.TypeSafeMatcher;
-import org.junit.runner.Description;
-import org.junit.runner.Result;
-import org.junit.runner.notification.Failure;
-import org.junit.runner.notification.RunListener;
-
-public class EventCollector extends RunListener {
-    static Matcher<EventCollector> everyTestRunSuccessful() {
-        return allOf(hasNoFailure(), hasNoAssumptionFailure());
-    }
-
-    static Matcher<EventCollector> hasNumberOfFailures(
-            final int numberOfFailures) {
-        return new TypeSafeMatcher<EventCollector>() {
-            @Override
-            public boolean matchesSafely(EventCollector item) {
-                return item.fFailures.size() == numberOfFailures;
-            }
-
-            public void describeTo(org.hamcrest.Description description) {
-                description.appendText("has ");
-                description.appendValue(numberOfFailures);
-                description.appendText(" failures");
-            }
-
-            @Override
-            protected void describeMismatchSafely(EventCollector item,
-                    org.hamcrest.Description description) {
-                description.appendValue(item.fFailures.size());
-                description.appendText(" failures");
-            }
-        };
-    }
-
-    static Matcher<EventCollector> hasSingleFailure() {
-        return hasNumberOfFailures(1);
-    }
-
-    static Matcher<EventCollector> hasNoFailure() {
-        return hasNumberOfFailures(0);
-    }
-
-    private static Matcher<EventCollector> hasNumberOfAssumptionFailures(
-            final int numberOfFailures) {
-        return new TypeSafeMatcher<EventCollector>() {
-            @Override
-            public boolean matchesSafely(EventCollector item) {
-                return item.fAssumptionFailures.size() == numberOfFailures;
-            }
-
-            public void describeTo(org.hamcrest.Description description) {
-                description.appendText("has ");
-                description.appendValue(numberOfFailures);
-                description.appendText(" assumption failures");
-            }
-        };
-    }
-
-    static Matcher<EventCollector> hasSingleAssumptionFailure() {
-        return hasNumberOfAssumptionFailures(1);
-    }
-
-    static Matcher<EventCollector> hasNoAssumptionFailure() {
-        return hasNumberOfAssumptionFailures(0);
-    }
-
-    public static Matcher<EventCollector> hasSingleFailureWithMessage(String message) {
-        return hasSingleFailureWithMessage(equalTo(message));
-    }
-
-    static Matcher<EventCollector> hasSingleFailureWithMessage(
-            final Matcher<String> messageMatcher) {
-        return new TypeSafeMatcher<EventCollector>() {
-            @Override
-            public boolean matchesSafely(EventCollector item) {
-                return hasSingleFailure().matches(item)
-                        && messageMatcher.matches(item.fFailures.get(0)
-                        .getMessage());
-            }
-
-            public void describeTo(org.hamcrest.Description description) {
-                description.appendText("has single failure with message ");
-                messageMatcher.describeTo(description);
-            }
-
-            @Override
-            protected void describeMismatchSafely(EventCollector item,
-                    org.hamcrest.Description description) {
-                description.appendText("was ");
-                hasSingleFailure().describeMismatch(item, description);
-                description.appendText(": ");
-                boolean first= true;
-                for (Failure f : item.fFailures) {
-                    if (!first) {
-                        description.appendText(" ,");
-                    }
-                    description.appendText("'");
-                    description.appendText(f.getMessage());
-                    description.appendText("'");
-                    first= false;
-                }
-            }
-        };
-    }
-
-    static Matcher<EventCollector> failureIs(final Matcher<? super Throwable> exceptionMatcher) {
-        return new TypeSafeMatcher<EventCollector>() {
-            @Override
-            public boolean matchesSafely(EventCollector item) {
-                for (Failure f : item.fFailures) {
-                    return exceptionMatcher.matches(f.getException());
-                }
-                return false;
-            }
-
-            public void describeTo(org.hamcrest.Description description) {
-                description.appendText("failure is ");
-                exceptionMatcher.describeTo(description);
-            }
-        };
-    }
-
-    private final List<Description> fTestRunsStarted = new ArrayList<Description>();
-
-    private final List<Result> fTestRunsFinished = new ArrayList<Result>();
-
-    private final List<Description> fTestsStarted = new ArrayList<Description>();
-
-    private final List<Description> fTestsFinished = new ArrayList<Description>();
-
-    private final List<Failure> fFailures = new ArrayList<Failure>();
-
-    private final List<Failure> fAssumptionFailures = new ArrayList<Failure>();
-
-    private final List<Description> fTestsIgnored = new ArrayList<Description>();
-
-    @Override
-    public void testRunStarted(Description description) throws Exception {
-        fTestRunsStarted.add(description);
-    }
-
-    @Override
-    public void testRunFinished(Result result) throws Exception {
-        fTestRunsFinished.add(result);
-    }
-
-    @Override
-    public void testStarted(Description description) throws Exception {
-        fTestsStarted.add(description);
-    }
-
-    @Override
-    public void testFinished(Description description) throws Exception {
-        fTestsFinished.add(description);
-    }
-
-    @Override
-    public void testFailure(Failure failure) throws Exception {
-        fFailures.add(failure);
-    }
-
-    @Override
-    public void testAssumptionFailure(Failure failure) {
-        fAssumptionFailures.add(failure);
-    }
-
-    @Override
-    public void testIgnored(Description description) throws Exception {
-        fTestsIgnored.add(description);
-    }
-
-    @Override
-    public String toString() {
-        return fTestRunsStarted.size() + " test runs started, "
-            + fTestRunsFinished.size() + " test runs finished, "
-            + fTestsStarted.size() + " tests started, "
-            + fTestsFinished.size() + " tests finished, "
-            + fFailures.size() + " failures, "
-            + fAssumptionFailures.size() + " assumption failures, "
-            + fTestsIgnored.size() + " tests ignored";
-    }
-}
diff --git a/src/test/java/org/junit/rules/ExpectedExceptionTest.java b/src/test/java/org/junit/rules/ExpectedExceptionTest.java
index fc548a81a155..98e13bf40751 100644
--- a/src/test/java/org/junit/rules/ExpectedExceptionTest.java
+++ b/src/test/java/org/junit/rules/ExpectedExceptionTest.java
@@ -11,10 +11,10 @@
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 import static org.junit.rules.ExpectedException.none;
-import static org.junit.rules.EventCollector.everyTestRunSuccessful;
-import static org.junit.rules.EventCollector.hasSingleAssumptionFailure;
-import static org.junit.rules.EventCollector.hasSingleFailure;
-import static org.junit.rules.EventCollector.hasSingleFailureWithMessage;
+import static org.junit.testsupport.EventCollectorMatchers.everyTestRunSuccessful;
+import static org.junit.testsupport.EventCollectorMatchers.hasSingleAssumptionFailure;
+import static org.junit.testsupport.EventCollectorMatchers.hasSingleFailure;
+import static org.junit.testsupport.EventCollectorMatchers.hasSingleFailureWithMessage;
 
 import java.util.Collection;
 
@@ -26,6 +26,7 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
+import org.junit.testsupport.EventCollector;
 
 @RunWith(Parameterized.class)
 public class ExpectedExceptionTest {
diff --git a/src/test/java/org/junit/runner/FilterOptionIntegrationTest.java b/src/test/java/org/junit/runner/FilterOptionIntegrationTest.java
index 25cde879385c..4c2e4f37e35a 100644
--- a/src/test/java/org/junit/runner/FilterOptionIntegrationTest.java
+++ b/src/test/java/org/junit/runner/FilterOptionIntegrationTest.java
@@ -1,20 +1,20 @@
 package org.junit.runner;
 
-import java.util.HashSet;
-import java.util.Set;
-
+import org.hamcrest.Matcher;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 import org.junit.experimental.categories.ExcludeCategories;
 import org.junit.experimental.categories.IncludeCategories;
-import org.junit.runner.notification.RunListener;
 import org.junit.tests.TestSystem;
+import org.junit.testsupport.EventCollector;
 
+import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.testsupport.EventCollectorMatchers.hasTestFinished;
+import static org.junit.testsupport.EventCollectorMatchers.hasTestStarted;
 
 public class FilterOptionIntegrationTest {
     private static final String INCLUDES_DUMMY_CATEGORY_0 = "--filter=" +
@@ -23,7 +23,7 @@ public class FilterOptionIntegrationTest {
             ExcludeCategories.class.getName() + "=" + DummyCategory1.class.getName();
 
     private JUnitCore jUnitCore = new JUnitCore();
-    private TestListener testListener = new TestListener();
+    private EventCollector testListener = new EventCollector();
 
     @Before
     public void setUp() {
@@ -111,40 +111,15 @@ private Result runJUnit(final String... args) {
     }
 
     private void assertWasRun(Class<?> testClass) {
-        assertTrue(testClass.getName() + " expected to finish but did not", testListener.wasRun(testClass));
+        assertThat(testListener, wasRun(testClass));
     }
 
     private void assertWasNotRun(Class<?> testClass) {
-        assertFalse(
-                testClass.getName() + " expected not to have been started but was",
-                testListener.wasRun(testClass));
+        assertThat(testListener, not(wasRun(testClass)));
     }
 
-    private static class TestListener extends RunListener {
-        private Set<String> startedTests = new HashSet<String>();
-        private Set<String> finishedTests = new HashSet<String>();
-
-        @Override
-        public void testFinished(final Description description) {
-            finishedTests.add(description.getClassName());
-        }
-
-        private boolean testFinished(final Class<?> testClass) {
-            return finishedTests.contains(testClass.getName());
-        }
-
-        @Override
-        public void testStarted(final Description description) {
-            startedTests.add(description.getClassName());
-        }
-
-        private boolean testStarted(final Class<?> testClass) {
-            return startedTests.contains(testClass.getName());
-        }
-
-        public boolean wasRun(final Class<?> testClass) {
-            return testStarted(testClass) && testFinished(testClass);
-        }
+    private Matcher<EventCollector> wasRun(Class<?> testClass) {
+        return allOf(hasTestStarted(testClass), hasTestFinished(testClass));
     }
 
     public static class DummyTestClass {
diff --git a/src/test/java/org/junit/runner/RequestTest.java b/src/test/java/org/junit/runner/RequestTest.java
index dc6a81108397..56c4fe998e1b 100644
--- a/src/test/java/org/junit/runner/RequestTest.java
+++ b/src/test/java/org/junit/runner/RequestTest.java
@@ -2,10 +2,10 @@
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.rules.EventCollector.hasSingleFailureWithMessage;
+import static org.junit.testsupport.EventCollectorMatchers.hasSingleFailureWithMessage;
 
 import org.junit.Test;
-import org.junit.rules.EventCollector;
+import org.junit.testsupport.EventCollector;
 import org.junit.runners.model.InitializationError;
 import org.junit.runners.model.RunnerBuilder;
 
diff --git a/src/test/java/org/junit/runner/notification/ConcurrentRunNotifierTest.java b/src/test/java/org/junit/runner/notification/ConcurrentRunNotifierTest.java
index 3406ed91150f..ee85685b5b17 100644
--- a/src/test/java/org/junit/runner/notification/ConcurrentRunNotifierTest.java
+++ b/src/test/java/org/junit/runner/notification/ConcurrentRunNotifierTest.java
@@ -1,21 +1,22 @@
 package org.junit.runner.notification;
 
-import org.junit.Test;
-import org.junit.runner.Description;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.testsupport.EventCollectorMatchers.hasNumberOfTestsStarted;
 
 import java.util.Random;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.CyclicBarrier;
-import java.util.concurrent.Executors;
 import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
 
-import static org.hamcrest.core.Is.is;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.testsupport.EventCollector;
 
 /**
  * Testing RunNotifier in concurrent access.
@@ -26,21 +27,13 @@
  */
 public final class ConcurrentRunNotifierTest {
     private static final long TIMEOUT = 3;
-    private final RunNotifier fNotifier = new RunNotifier();
 
-    private static class ConcurrentRunListener extends RunListener {
-        final AtomicInteger fTestStarted = new AtomicInteger(0);
-
-        @Override
-        public void testStarted(Description description) throws Exception {
-            fTestStarted.incrementAndGet();
-        }
-    }
+    private final RunNotifier fNotifier = new RunNotifier();
 
     @Test
     public void realUsage() throws Exception {
-        ConcurrentRunListener listener1 = new ConcurrentRunListener();
-        ConcurrentRunListener listener2 = new ConcurrentRunListener();
+        EventCollector listener1 = new EventCollector();
+        EventCollector listener2 = new EventCollector();
         fNotifier.addListener(listener1);
         fNotifier.addListener(listener2);
 
@@ -59,8 +52,8 @@ public void run() {
         fNotifier.removeListener(listener1);
         fNotifier.removeListener(listener2);
 
-        assertThat(listener1.fTestStarted.get(), is(numParallelTests));
-        assertThat(listener2.fTestStarted.get(), is(numParallelTests));
+        assertThat(listener1, hasNumberOfTestsStarted(numParallelTests));
+        assertThat(listener2, hasNumberOfTestsStarted(numParallelTests));
     }
 
     private static class ExaminedListener extends RunListener {
diff --git a/src/test/java/org/junit/runner/notification/RunNotifierTest.java b/src/test/java/org/junit/runner/notification/RunNotifierTest.java
index 531754f24e99..495643c7ac4b 100644
--- a/src/test/java/org/junit/runner/notification/RunNotifierTest.java
+++ b/src/test/java/org/junit/runner/notification/RunNotifierTest.java
@@ -1,34 +1,31 @@
 package org.junit.runner.notification;
 
 import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.hamcrest.core.Is.is;
-import static org.junit.Assert.assertNotNull;
+import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertThat;
-
-import java.util.concurrent.atomic.AtomicInteger;
-
+import static org.junit.testsupport.EventCollectorMatchers.hasNoFailure;
+import static org.junit.testsupport.EventCollectorMatchers.hasNumberOfTestsStarted;
 import org.junit.Test;
-import org.junit.runner.Description;
 import org.junit.runner.Result;
+import org.junit.testsupport.EventCollector;
 
 public class RunNotifierTest {
     private final RunNotifier fNotifier = new RunNotifier();
 
     @Test
     public void notifiesSecondListenerIfFirstThrowsException() {
-        FailureListener failureListener = new FailureListener();
+        EventCollector eventCollector = new EventCollector();
         fNotifier.addListener(new CorruptListener());
-        fNotifier.addListener(failureListener);
+        fNotifier.addListener(eventCollector);
         fNotifier.fireTestFailure(new Failure(null, null));
-        assertNotNull("The FailureListener registered no failure.",
-                failureListener.failure);
+        assertThat(eventCollector, not(hasNoFailure()));
     }
 
     @Test
     public void hasNoProblemsWithFailingListeners() { // see issues 209 and 395
         fNotifier.addListener(new CorruptListener());
-        fNotifier.addListener(new FailureListener());
+        fNotifier.addListener(new EventCollector());
         fNotifier.addListener(new CorruptListener());
         fNotifier.fireTestRunFinished(new Result());
     }
@@ -44,53 +41,53 @@ public void testFailure(Failure failure) throws Exception {
             throw new RuntimeException();
         }
     }
-    
+
     @Test
     public void addAndRemoveWithNonThreadSafeListener() {
-        CountingListener listener = new CountingListener();
-        assertThat(listener.fTestStarted.get(), is(0));
+        EventCollector listener = new EventCollector();
+        assertThat(listener, hasNumberOfTestsStarted(0));
         fNotifier.addListener(listener);
         fNotifier.fireTestStarted(null);
-        assertThat(listener.fTestStarted.get(), is(1));
+        assertThat(listener, hasNumberOfTestsStarted(1));
         fNotifier.removeListener(listener);
         fNotifier.fireTestStarted(null);
-        assertThat(listener.fTestStarted.get(), is(1));
+        assertThat(listener, hasNumberOfTestsStarted(1));
     }
 
     @Test
     public void addFirstAndRemoveWithNonThreadSafeListener() {
-        CountingListener listener = new CountingListener();
-        assertThat(listener.fTestStarted.get(), is(0));
+        EventCollector listener = new EventCollector();
+        assertThat(listener, hasNumberOfTestsStarted(0));
         fNotifier.addFirstListener(listener);
         fNotifier.fireTestStarted(null);
-        assertThat(listener.fTestStarted.get(), is(1));
+        assertThat(listener, hasNumberOfTestsStarted(1));
         fNotifier.removeListener(listener);
         fNotifier.fireTestStarted(null);
-        assertThat(listener.fTestStarted.get(), is(1));
+        assertThat(listener, hasNumberOfTestsStarted(1));
     }
-    
+
     @Test
     public void addAndRemoveWithThreadSafeListener() {
         ThreadSafeListener listener = new ThreadSafeListener();
-        assertThat(listener.fTestStarted.get(), is(0));
+        assertThat(listener, hasNumberOfTestsStarted(0));
         fNotifier.addListener(listener);
         fNotifier.fireTestStarted(null);
-        assertThat(listener.fTestStarted.get(), is(1));
+        assertThat(listener, hasNumberOfTestsStarted(1));
         fNotifier.removeListener(listener);
         fNotifier.fireTestStarted(null);
-        assertThat(listener.fTestStarted.get(), is(1));
+        assertThat(listener, hasNumberOfTestsStarted(1));
     }
 
     @Test
     public void addFirstAndRemoveWithThreadSafeListener() {
         ThreadSafeListener listener = new ThreadSafeListener();
-        assertThat(listener.fTestStarted.get(), is(0));
+        assertThat(listener, hasNumberOfTestsStarted(0));
         fNotifier.addFirstListener(listener);
         fNotifier.fireTestStarted(null);
-        assertThat(listener.fTestStarted.get(), is(1));
+        assertThat(listener, hasNumberOfTestsStarted(1));
         fNotifier.removeListener(listener);
         fNotifier.fireTestStarted(null);
-        assertThat(listener.fTestStarted.get(), is(1));
+        assertThat(listener, hasNumberOfTestsStarted(1));
     }
 
     @Test
@@ -101,31 +98,13 @@ public void wrapIfNotThreadSafeShouldNotWrapThreadSafeListeners() {
 
     @Test
     public void wrapIfNotThreadSafeShouldWrapNonThreadSafeListeners() {
-        CountingListener listener = new CountingListener();
+        EventCollector listener = new EventCollector();
         RunListener wrappedListener = new RunNotifier().wrapIfNotThreadSafe(listener);
         assertThat(wrappedListener, instanceOf(SynchronizedRunListener.class));
     }
 
-    private static class FailureListener extends RunListener {
-        private Failure failure;
-
-        @Override
-        public void testFailure(Failure failure) throws Exception {
-            this.failure = failure;
-        }
-    }
-    
-    private static class CountingListener extends RunListener {
-        final AtomicInteger fTestStarted = new AtomicInteger(0);
-
-        @Override
-        public void testStarted(Description description) throws Exception {
-            fTestStarted.incrementAndGet();
-        }
-    }
-    
     @RunListener.ThreadSafe
-    private static class ThreadSafeListener extends CountingListener {
+    private static class ThreadSafeListener extends EventCollector {
     }
 
 }
diff --git a/src/test/java/org/junit/tests/AllTests.java b/src/test/java/org/junit/tests/AllTests.java
index fd9169121d1a..f6a229d4f685 100644
--- a/src/test/java/org/junit/tests/AllTests.java
+++ b/src/test/java/org/junit/tests/AllTests.java
@@ -20,6 +20,7 @@
 import org.junit.tests.manipulation.AllManipulationTests;
 import org.junit.tests.running.AllRunningTests;
 import org.junit.tests.validation.AllValidationTests;
+import org.junit.testsupport.AllTestSupportTests;
 import org.junit.validator.AllValidatorTests;
 
 @RunWith(Suite.class)
@@ -37,6 +38,7 @@
         AllRunnerTests.class,
         AllRunningTests.class,
         AllSamplesTests.class,
+        AllTestSupportTests.class,
         AllValidationTests.class,
         AllValidatorTests.class,
         AssumptionViolatedExceptionTest.class,
diff --git a/src/test/java/org/junit/tests/experimental/AssumptionTest.java b/src/test/java/org/junit/tests/experimental/AssumptionTest.java
index 4b9f69a545a0..7e394ed7d046 100644
--- a/src/test/java/org/junit/tests/experimental/AssumptionTest.java
+++ b/src/test/java/org/junit/tests/experimental/AssumptionTest.java
@@ -2,7 +2,7 @@
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertEquals;
+import static org.hamcrest.CoreMatchers.startsWith;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
@@ -13,8 +13,9 @@
 import static org.junit.Assume.assumeTrue;
 import static org.junit.experimental.results.PrintableResult.testResult;
 import static org.junit.experimental.results.ResultMatchers.isSuccessful;
+import static org.junit.testsupport.EventCollectorMatchers.hasSingleAssumptionFailure;
+import static org.junit.testsupport.EventCollectorMatchers.hasSingleAssumptionFailureWithMessage;
 
-import java.util.ArrayList;
 import java.util.List;
 
 import org.junit.Assume;
@@ -25,9 +26,11 @@
 import org.junit.runner.JUnitCore;
 import org.junit.runner.Result;
 import org.junit.runner.notification.Failure;
-import org.junit.runner.notification.RunListener;
+import org.junit.testsupport.EventCollector;
 
 public class AssumptionTest {
+    private final static String DUMMY_MESSAGE = "Some random DUMMY_MESSAGE string.";
+
     public static class HasFailingAssumption {
         @Test
         public void assumptionsFail() {
@@ -44,21 +47,10 @@ public void failedAssumptionsMeanPassing() {
         assertThat(result.getFailureCount(), is(0));
     }
 
-    private static int assumptionFailures = 0;
-
     @Test
     public void failedAssumptionsCanBeDetectedByListeners() {
-        assumptionFailures = 0;
-        JUnitCore core = new JUnitCore();
-        core.addListener(new RunListener() {
-            @Override
-            public void testAssumptionFailure(Failure failure) {
-                assumptionFailures++;
-            }
-        });
-        core.run(HasFailingAssumption.class);
-
-        assertThat(assumptionFailures, is(1));
+        EventCollector eventCollector = runTestClass(HasFailingAssumption.class);
+        assertThat(eventCollector, hasSingleAssumptionFailure());
     }
 
     public static class HasPassingAssumption {
@@ -218,7 +210,6 @@ public void assumeWithExpectedException() {
         assumeTrue(false);
     }
 
-    final static String message = "Some random message string.";
     final static Throwable e = new Throwable();
 
     /**
@@ -227,16 +218,15 @@ public void assumeWithExpectedException() {
     public static class HasAssumeWithMessage {
         @Test
         public void testMethod() {
-            assumeTrue(message, false);
+            assumeTrue(DUMMY_MESSAGE, false);
         }
     }
 
     @Test
     public void assumptionsWithMessage() {
-        final List<Failure> failures =
-                runAndGetAssumptionFailures(HasAssumeWithMessage.class);
-
-        assertTrue(failures.get(0).getMessage().contains(message));
+        EventCollector eventCollector = runTestClass(HasAssumeWithMessage.class);
+        assertThat(eventCollector,
+                hasSingleAssumptionFailureWithMessage(DUMMY_MESSAGE));
     }
 
     /**
@@ -245,49 +235,42 @@ public void assumptionsWithMessage() {
     public static class HasAssumeWithMessageAndCause {
         @Test
         public void testMethod() {
-            assumeNoException(message, e);
+            assumeNoException(DUMMY_MESSAGE, e);
         }
     }
 
     @Test
     public void assumptionsWithMessageAndCause() {
-        final List<Failure> failures =
-                runAndGetAssumptionFailures(HasAssumeWithMessageAndCause.class);
-        assertTrue(failures.get(0).getMessage().contains(message));
+        List<Failure> failures = runTestClass(
+                HasAssumeWithMessageAndCause.class).getAssumptionFailures();
+        assertTrue(failures.get(0).getMessage().contains(DUMMY_MESSAGE));
         assertSame(failures.get(0).getException().getCause(), e);
     }
 
     public static class HasFailingAssumptionWithMessage {
         @Test
         public void assumptionsFail() {
-            assumeThat(message, 3, is(4));
+            assumeThat(DUMMY_MESSAGE, 3, is(4));
             fail();
         }
     }
 
     @Test
     public void failedAssumptionsWithMessage() {
-        final List<Failure> failures =
-                runAndGetAssumptionFailures(HasFailingAssumptionWithMessage.class);
-
-        assertEquals(1, failures.size());
-        assertTrue(failures.get(0).getMessage().contains(message));
+        EventCollector eventCollector = runTestClass(HasFailingAssumptionWithMessage.class);
+        assertThat(eventCollector,
+                hasSingleAssumptionFailureWithMessage(startsWith(DUMMY_MESSAGE)));
     }
 
     /**
      * Helper method that runs tests on <code>clazz</code> and returns any
      * {@link Failure} objects that were {@link AssumptionViolatedException}s.
      */
-    private static List<Failure> runAndGetAssumptionFailures(Class<?> clazz) {
-        final List<Failure> failures = new ArrayList<Failure>();
-        final JUnitCore core = new JUnitCore();
-        core.addListener(new RunListener() {
-            @Override
-            public void testAssumptionFailure(Failure failure) {
-                failures.add(failure);
-            }
-        });
+    private static EventCollector runTestClass(Class<?> clazz) {
+        EventCollector eventCollector = new EventCollector();
+        JUnitCore core = new JUnitCore();
+        core.addListener(eventCollector);
         core.run(clazz);
-        return failures;
+        return eventCollector;
     }
 }
diff --git a/src/test/java/org/junit/tests/experimental/max/MaxStarterTest.java b/src/test/java/org/junit/tests/experimental/max/MaxStarterTest.java
index 9c8008012270..5667594d0d7b 100644
--- a/src/test/java/org/junit/tests/experimental/max/MaxStarterTest.java
+++ b/src/test/java/org/junit/tests/experimental/max/MaxStarterTest.java
@@ -8,7 +8,6 @@
 import static org.junit.Assert.fail;
 
 import java.io.File;
-import java.util.ArrayList;
 import java.util.List;
 
 import junit.framework.TestCase;
@@ -25,8 +24,8 @@
 import org.junit.runner.Runner;
 import org.junit.runner.manipulation.Filter;
 import org.junit.runner.notification.Failure;
-import org.junit.runner.notification.RunListener;
 import org.junit.tests.AllTests;
+import org.junit.testsupport.EventCollector;
 
 public class MaxStarterTest {
     private MaxCore fMax;
@@ -159,16 +158,11 @@ public void preferFast() {
     @Test
     public void listenersAreCalledCorrectlyInTheFaceOfFailures()
             throws Exception {
+        EventCollector listener = new EventCollector();
         JUnitCore core = new JUnitCore();
-        final List<Failure> failures = new ArrayList<Failure>();
-        core.addListener(new RunListener() {
-            @Override
-            public void testRunFinished(Result result) throws Exception {
-                failures.addAll(result.getFailures());
-            }
-        });
+        core.addListener(listener);
         fMax.run(Request.aClass(TwoTests.class), core);
-        assertEquals(1, failures.size());
+        assertEquals(1, listener.getTestRunsFinished().get(0).getFailureCount());
     }
 
     @Test
@@ -276,7 +270,6 @@ public void halfMalformed() {
                 .getFailureCount(), is(1));
     }
 
-
     @Test
     public void correctErrorFromHalfMalformedTest() {
         Request request = Request.aClass(HalfMalformedJUnit38TestMethod.class);
diff --git a/src/test/java/org/junit/tests/junit3compatibility/JUnit38ClassRunnerTest.java b/src/test/java/org/junit/tests/junit3compatibility/JUnit38ClassRunnerTest.java
index b7a1c515e3f2..8ae8c1cf5a90 100644
--- a/src/test/java/org/junit/tests/junit3compatibility/JUnit38ClassRunnerTest.java
+++ b/src/test/java/org/junit/tests/junit3compatibility/JUnit38ClassRunnerTest.java
@@ -3,6 +3,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.testsupport.EventCollectorMatchers.hasNumberOfTestsStarted;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -19,9 +21,9 @@
 import org.junit.runner.JUnitCore;
 import org.junit.runner.Result;
 import org.junit.runner.notification.Failure;
-import org.junit.runner.notification.RunListener;
 import org.junit.runner.manipulation.Filter;
 import org.junit.runner.manipulation.NoTestsRemainException;
+import org.junit.testsupport.EventCollector;
 
 public class JUnit38ClassRunnerTest {
     public static class MyTest extends TestCase {
@@ -51,8 +53,6 @@ public void canUnadaptAnAdapter() {
         assertEquals(Description.createTestDescription(AnnotatedTest.class, "foo"), failure.getDescription());
     }
 
-    static int count;
-
     static public class OneTest extends TestCase {
         public void testOne() {
         }
@@ -60,20 +60,13 @@ public void testOne() {
 
     @Test
     public void testListener() throws Exception {
+        EventCollector eventCollector = new EventCollector();
         JUnitCore runner = new JUnitCore();
-        RunListener listener = new RunListener() {
-            @Override
-            public void testStarted(Description description) {
-                assertEquals(Description.createTestDescription(OneTest.class, "testOne"),
-                        description);
-                count++;
-            }
-        };
-
-        runner.addListener(listener);
-        count = 0;
+        runner.addListener(eventCollector);
         Result result = runner.run(OneTest.class);
-        assertEquals(1, count);
+        assertThat(eventCollector, hasNumberOfTestsStarted(1));
+        assertEquals(Description.createTestDescription(OneTest.class, "testOne"),
+                eventCollector.getTestsStarted().get(0));
         assertEquals(1, result.getRunCount());
     }
 
diff --git a/src/test/java/org/junit/tests/listening/RunnerTest.java b/src/test/java/org/junit/tests/listening/RunnerTest.java
index cd12c350ae95..6a93f90a5133 100644
--- a/src/test/java/org/junit/tests/listening/RunnerTest.java
+++ b/src/test/java/org/junit/tests/listening/RunnerTest.java
@@ -1,28 +1,15 @@
 package org.junit.tests.listening;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.testsupport.EventCollectorMatchers.hasNumberOfTestRunsFinished;
+import static org.junit.testsupport.EventCollectorMatchers.hasNumberOfTestRunsStarted;
 
 import junit.framework.TestCase;
 import org.junit.Test;
-import org.junit.runner.Description;
 import org.junit.runner.JUnitCore;
-import org.junit.runner.notification.RunListener;
+import org.junit.testsupport.EventCollector;
 
 public class RunnerTest {
-
-    private boolean wasRun;
-
-    public class MyListener extends RunListener {
-
-        int testCount;
-
-        @Override
-        public void testRunStarted(Description description) {
-            this.testCount = description.testCount();
-        }
-    }
-
     public static class Example {
         @Test
         public void empty() {
@@ -31,11 +18,8 @@ public void empty() {
 
     @Test
     public void newTestCount() {
-        JUnitCore runner = new JUnitCore();
-        MyListener listener = new MyListener();
-        runner.addListener(listener);
-        runner.run(Example.class);
-        assertEquals(1, listener.testCount);
+        EventCollector eventCollector = runTest(Example.class);
+        assertThat(eventCollector, hasNumberOfTestRunsStarted(1));
     }
 
     public static class ExampleTest extends TestCase {
@@ -45,11 +29,8 @@ public void testEmpty() {
 
     @Test
     public void oldTestCount() {
-        JUnitCore runner = new JUnitCore();
-        MyListener listener = new MyListener();
-        runner.addListener(listener);
-        runner.run(ExampleTest.class);
-        assertEquals(1, listener.testCount);
+        EventCollector eventCollector = runTest(ExampleTest.class);
+        assertThat(eventCollector, hasNumberOfTestRunsStarted(1));
     }
 
     public static class NewExample {
@@ -60,16 +41,15 @@ public void empty() {
 
     @Test
     public void testFinished() {
+        EventCollector eventCollector = runTest(NewExample.class);
+        assertThat(eventCollector, hasNumberOfTestRunsFinished(1));
+    }
+
+    private EventCollector runTest(Class<?> testClass) {
+        EventCollector eventCollector = new EventCollector();
         JUnitCore runner = new JUnitCore();
-        wasRun = false;
-        RunListener listener = new MyListener() {
-            @Override
-            public void testFinished(Description description) {
-                wasRun = true;
-            }
-        };
-        runner.addListener(listener);
-        runner.run(NewExample.class);
-        assertTrue(wasRun);
+        runner.addListener(eventCollector);
+        runner.run(testClass);
+        return eventCollector;
     }
 }
diff --git a/src/test/java/org/junit/tests/running/classes/ParameterizedTestTest.java b/src/test/java/org/junit/tests/running/classes/ParameterizedTestTest.java
index 6f610e4fa8ac..112afd4f5ba9 100644
--- a/src/test/java/org/junit/tests/running/classes/ParameterizedTestTest.java
+++ b/src/test/java/org/junit/tests/running/classes/ParameterizedTestTest.java
@@ -9,6 +9,7 @@
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 import static org.junit.experimental.results.PrintableResult.testResult;
+import static org.junit.testsupport.EventCollectorMatchers.*;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -22,6 +23,7 @@
 import org.junit.BeforeClass;
 import org.junit.FixMethodOrder;
 import org.junit.Test;
+import org.junit.rules.ErrorCollector;
 import org.junit.runner.Description;
 import org.junit.runner.JUnitCore;
 import org.junit.runner.Request;
@@ -38,6 +40,8 @@
 import org.junit.runners.model.InitializationError;
 import org.junit.runners.parameterized.ParametersRunnerFactory;
 import org.junit.runners.parameterized.TestWithParameters;
+import org.junit.testsupport.EventCollector;
+import org.junit.testsupport.EventCollectorMatchers;
 
 public class ParameterizedTestTest {
     @RunWith(Parameterized.class)
@@ -797,18 +801,12 @@ public void assumtionViolationInParameters() {
         assertEquals(2, successResult.getRunCount());
 
         ParameterizedAssumtionViolation.condition = false;
+        EventCollector collector = new EventCollector();
         JUnitCore core = new JUnitCore();
-        final List<Failure> assumptionFailures = new ArrayList<Failure>();
-        core.addListener(new RunListener() {
-            @Override
-            public void testAssumptionFailure(Failure failure) {
-                assumptionFailures.add(failure);
-            }
-        });
-        Result failureResult = core.run(ParameterizedAssumtionViolation.class);
-        assertTrue(failureResult.wasSuccessful());
-        assertEquals(0, failureResult.getRunCount());
-        assertEquals(0, failureResult.getIgnoreCount());
-        assertEquals(1, assumptionFailures.size());
+        core.addListener(collector);
+        core.run(ParameterizedAssumtionViolation.class);
+        assertThat(collector, allOf(
+                hasNoFailure(), hasSingleAssumptionFailure(),
+                hasNumberOfTestsStarted(0), hasNumberOfTestsFinished(0)));
     }
 }
diff --git a/src/test/java/org/junit/tests/running/classes/ParentRunnerTest.java b/src/test/java/org/junit/tests/running/classes/ParentRunnerTest.java
index a8eabbe8ccdf..3d4e5833d1f6 100644
--- a/src/test/java/org/junit/tests/running/classes/ParentRunnerTest.java
+++ b/src/test/java/org/junit/tests/running/classes/ParentRunnerTest.java
@@ -1,15 +1,16 @@
 package org.junit.tests.running.classes;
 
-import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
+import static org.junit.testsupport.EventCollectorMatchers.*;
 
 import java.util.List;
 
 import org.hamcrest.Matcher;
 import org.hamcrest.TypeSafeMatcher;
-import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -19,8 +20,6 @@
 import org.junit.runner.Request;
 import org.junit.runner.Result;
 import org.junit.runner.manipulation.Filter;
-import org.junit.runner.notification.Failure;
-import org.junit.runner.notification.RunListener;
 import org.junit.runner.notification.RunNotifier;
 import org.junit.runners.BlockJUnit4ClassRunner;
 import org.junit.runners.ParentRunner;
@@ -28,6 +27,7 @@
 import org.junit.runners.model.RunnerScheduler;
 import org.junit.rules.RuleMemberValidatorTest.TestWithNonStaticClassRule;
 import org.junit.rules.RuleMemberValidatorTest.TestWithProtectedClassRule;
+import org.junit.testsupport.EventCollector;
 
 public class ParentRunnerTest {
     public static String log = "";
@@ -169,17 +169,10 @@ public void test() {}
 
     @Test
     public void assertionErrorAtParentLevelTest() throws InitializationError {
-        CountingRunListener countingRunListener = runTestWithParentRunner(AssertionErrorAtParentLevelTest.class);
-        Assert.assertEquals(1, countingRunListener.testSuiteStarted);
-        Assert.assertEquals(1, countingRunListener.testSuiteFinished);
-        Assert.assertEquals(1, countingRunListener.testSuiteFailure);
-        Assert.assertEquals(0, countingRunListener.testSuiteAssumptionFailure);
-
-        Assert.assertEquals(0, countingRunListener.testStarted);
-        Assert.assertEquals(0, countingRunListener.testFinished);
-        Assert.assertEquals(0, countingRunListener.testFailure);
-        Assert.assertEquals(0, countingRunListener.testAssumptionFailure);
-        Assert.assertEquals(0, countingRunListener.testIgnored);
+        EventCollector collector = runTestWithParentRunner(AssertionErrorAtParentLevelTest.class);
+        assertThat(collector, allOf(hasNoAssumptionFailure(), hasSingleFailure(),
+                hasNumberOfTestsIgnored(0), hasNumberOfTestsFinished(0), hasNumberOfTestsStarted(0),
+                hasNumberOfTestSuitesFinished(1), hasNumberOfTestSuiteStarted(1)));
     }
 
     public static class AssumptionViolatedAtParentLevelTest {
@@ -195,17 +188,10 @@ public void test() {}
 
     @Test
     public void assumptionViolatedAtParentLevel() throws InitializationError {
-        CountingRunListener countingRunListener = runTestWithParentRunner(AssumptionViolatedAtParentLevelTest.class);
-        Assert.assertEquals(1, countingRunListener.testSuiteStarted);
-        Assert.assertEquals(1, countingRunListener.testSuiteFinished);
-        Assert.assertEquals(0, countingRunListener.testSuiteFailure);
-        Assert.assertEquals(1, countingRunListener.testSuiteAssumptionFailure);
-
-        Assert.assertEquals(0, countingRunListener.testStarted);
-        Assert.assertEquals(0, countingRunListener.testFinished);
-        Assert.assertEquals(0, countingRunListener.testFailure);
-        Assert.assertEquals(0, countingRunListener.testAssumptionFailure);
-        Assert.assertEquals(0, countingRunListener.testIgnored);
+        EventCollector collector = runTestWithParentRunner(AssumptionViolatedAtParentLevelTest.class);
+        assertThat(collector, allOf(hasSingleAssumptionFailure(), hasNoFailure(),
+                hasNumberOfTestsIgnored(0), hasNumberOfTestsFinished(0), hasNumberOfTestsStarted(0),
+                hasNumberOfTestSuitesFinished(1), hasNumberOfTestSuiteStarted(1)));
     }
 
     public static class TestTest {
@@ -230,81 +216,18 @@ public void assumptionFail() {
 
     @Test
     public void parentRunnerTestMethods() throws InitializationError {
-        CountingRunListener countingRunListener = runTestWithParentRunner(TestTest.class);
-        Assert.assertEquals(1, countingRunListener.testSuiteStarted);
-        Assert.assertEquals(1, countingRunListener.testSuiteFinished);
-        Assert.assertEquals(0, countingRunListener.testSuiteFailure);
-        Assert.assertEquals(0, countingRunListener.testSuiteAssumptionFailure);
-
-        Assert.assertEquals(3, countingRunListener.testStarted);
-        Assert.assertEquals(3, countingRunListener.testFinished);
-        Assert.assertEquals(1, countingRunListener.testFailure);
-        Assert.assertEquals(1, countingRunListener.testAssumptionFailure);
-        Assert.assertEquals(1, countingRunListener.testIgnored);
+        EventCollector collector = runTestWithParentRunner(TestTest.class);
+        assertThat(collector, allOf(hasSingleAssumptionFailure(), hasSingleFailure(),
+                hasNumberOfTestsIgnored(1), hasNumberOfTestsFinished(3), hasNumberOfTestsStarted(3),
+                hasNumberOfTestSuitesFinished(1), hasNumberOfTestSuiteStarted(1)));
     }
 
-    private CountingRunListener runTestWithParentRunner(Class<?> testClass) throws InitializationError {
-        CountingRunListener listener = new CountingRunListener();
+    private EventCollector runTestWithParentRunner(Class<?> testClass) throws InitializationError {
+        EventCollector collector = new EventCollector();
         RunNotifier runNotifier = new RunNotifier();
-        runNotifier.addListener(listener);
-        ParentRunner<?> runner = new BlockJUnit4ClassRunner(testClass);
+        runNotifier.addListener(collector);
+        ParentRunner runner = new BlockJUnit4ClassRunner(testClass);
         runner.run(runNotifier);
-        return listener;
-    }
-
-    private static class CountingRunListener extends RunListener {
-        private int testSuiteStarted = 0;
-        private int testSuiteFinished = 0;
-        private int testSuiteFailure = 0;
-        private int testSuiteAssumptionFailure = 0;
-
-        private int testStarted = 0;
-        private int testFinished = 0;
-        private int testFailure = 0;
-        private int testAssumptionFailure = 0;
-        private int testIgnored = 0;
-
-        @Override
-        public void testSuiteStarted(Description description) throws Exception {
-            testSuiteStarted++;
-        }
-
-        @Override
-        public void testSuiteFinished(Description description) throws Exception {
-            testSuiteFinished++;
-        }
-
-        @Override
-        public void testStarted(Description description) throws Exception {
-            testStarted++;
-        }
-
-        @Override
-        public void testFinished(Description description) throws Exception {
-            testFinished++;
-        }
-
-        @Override
-        public void testFailure(Failure failure) throws Exception {
-            if (failure.getDescription().isSuite()) {
-                testSuiteFailure++;
-            } else {
-                testFailure++;
-            }
-        }
-
-        @Override
-        public void testAssumptionFailure(Failure failure) {
-            if (failure.getDescription().isSuite()) {
-                testSuiteAssumptionFailure++;
-            } else {
-                testAssumptionFailure++;
-            }
-        }
-
-        @Override
-        public void testIgnored(Description description) throws Exception {
-            testIgnored++;
-        }
+        return collector;
     }
 }
diff --git a/src/test/java/org/junit/testsupport/AllTestSupportTests.java b/src/test/java/org/junit/testsupport/AllTestSupportTests.java
new file mode 100644
index 000000000000..2663f9bb3442
--- /dev/null
+++ b/src/test/java/org/junit/testsupport/AllTestSupportTests.java
@@ -0,0 +1,12 @@
+package org.junit.testsupport;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+        EventCollectorMatchersTest.class,
+        EventCollectorTest.class
+})
+public class AllTestSupportTests {
+}
diff --git a/src/test/java/org/junit/testsupport/EventCollector.java b/src/test/java/org/junit/testsupport/EventCollector.java
new file mode 100644
index 000000000000..531c505ce283
--- /dev/null
+++ b/src/test/java/org/junit/testsupport/EventCollector.java
@@ -0,0 +1,130 @@
+package org.junit.testsupport;
+
+import static java.util.Collections.synchronizedList;
+import static java.util.Collections.unmodifiableList;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.runner.Description;
+import org.junit.runner.Result;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+
+/**
+ * A {@link org.junit.runner.notification.RunListener} that collects all events.
+ */
+public class EventCollector extends RunListener {
+
+    private final List<Description> testRunsStarted = synchronizedList(new ArrayList<Description>());
+
+    private final List<Result> testRunsFinished = synchronizedList(new ArrayList<Result>());
+
+    private final List<Description> testSuitesStarted = synchronizedList(new ArrayList<Description>());
+
+    private final List<Description> testSuitesFinished = synchronizedList(new ArrayList<Description>());
+
+    private final List<Description> testsStarted = synchronizedList(new ArrayList<Description>());
+
+    private final List<Description> testsFinished = synchronizedList(new ArrayList<Description>());
+
+    private final List<Failure> failures = synchronizedList(new ArrayList<Failure>());
+
+    private final List<Failure> assumptionFailures = synchronizedList(new ArrayList<Failure>());
+
+    private final List<Description> testsIgnored = synchronizedList(new ArrayList<Description>());
+
+    @Override
+    public void testRunStarted(Description description) {
+        testRunsStarted.add(description);
+    }
+
+    @Override
+    public void testRunFinished(Result result) {
+        testRunsFinished.add(result);
+    }
+
+    @Override
+    public void testSuiteStarted(Description description) {
+        testSuitesStarted.add(description);
+    }
+
+    @Override
+    public void testSuiteFinished(Description description) {
+        testSuitesFinished.add(description);
+    }
+
+    @Override
+    public void testStarted(Description description) {
+        testsStarted.add(description);
+    }
+
+    @Override
+    public void testFinished(Description description) {
+        testsFinished.add(description);
+    }
+
+    @Override
+    public void testFailure(Failure failure) {
+        failures.add(failure);
+    }
+
+    @Override
+    public void testAssumptionFailure(Failure failure) {
+        assumptionFailures.add(failure);
+    }
+
+    @Override
+    public void testIgnored(Description description) {
+        testsIgnored.add(description);
+    }
+
+    public List<Description> getTestRunsStarted() {
+        return unmodifiableList(testRunsStarted);
+    }
+
+    public List<Result> getTestRunsFinished() {
+        return unmodifiableList(testRunsFinished);
+    }
+
+    public List<Description> getTestSuitesStarted() {
+        return unmodifiableList(testSuitesStarted);
+    }
+
+    public List<Description> getTestSuitesFinished() {
+        return unmodifiableList(testSuitesFinished);
+    }
+
+    public List<Description> getTestsStarted() {
+        return unmodifiableList(testsStarted);
+    }
+
+    public List<Description> getTestsFinished() {
+        return unmodifiableList(testsFinished);
+    }
+
+    public List<Failure> getFailures() {
+        return unmodifiableList(failures);
+    }
+
+    public List<Failure> getAssumptionFailures() {
+        return unmodifiableList(assumptionFailures);
+    }
+
+    public List<Description> getTestsIgnored() {
+        return unmodifiableList(testsIgnored);
+    }
+
+    @Override
+    public String toString() {
+        return testRunsStarted.size() + " test runs started, "
+                + testRunsFinished.size() + " test runs finished, "
+                + testSuitesStarted.size() + " test suites started, "
+                + testSuitesFinished.size() + " test suites finished, "
+                + testsStarted.size() + " tests started, "
+                + testsFinished.size() + " tests finished, "
+                + failures.size() + " failures, " + assumptionFailures.size()
+                + " assumption failures, " + testsIgnored.size()
+                + " tests ignored";
+    }
+}
diff --git a/src/test/java/org/junit/testsupport/EventCollectorMatchers.java b/src/test/java/org/junit/testsupport/EventCollectorMatchers.java
new file mode 100644
index 000000000000..5e85c1fff037
--- /dev/null
+++ b/src/test/java/org/junit/testsupport/EventCollectorMatchers.java
@@ -0,0 +1,327 @@
+package org.junit.testsupport;
+
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.core.IsEqual.equalTo;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.runner.Description;
+import org.junit.runner.notification.Failure;
+
+public class EventCollectorMatchers {
+    public static Matcher<EventCollector> everyTestRunSuccessful() {
+        return allOf(hasNoFailure(), hasNoAssumptionFailure());
+    }
+
+    public static Matcher<EventCollector> hasNoAssumptionFailure() {
+        return hasNumberOfAssumptionFailures(0);
+    }
+
+    public static Matcher<EventCollector> hasSingleAssumptionFailure() {
+        return hasNumberOfAssumptionFailures(1);
+    }
+
+    public static Matcher<EventCollector> hasSingleAssumptionFailureWithMessage(
+            String message) {
+        return hasSingleAssumptionFailureWithMessage(equalTo(message));
+    }
+
+    public static Matcher<EventCollector> hasSingleAssumptionFailureWithMessage(
+            Matcher<String> messageMatcher) {
+        return new SingleFailureMatcher(messageMatcher, "assumption failure") {
+            @Override
+            List<Failure> getFailures(EventCollector collector) {
+                return collector.getAssumptionFailures();
+            }
+        };
+    }
+
+    public static Matcher<EventCollector> hasNumberOfAssumptionFailures(
+            int numberOfFailures) {
+        return new CountMatcher(numberOfFailures, "assumption failures") {
+            @Override
+            Collection<?> getItems(EventCollector collector) {
+                return collector.getAssumptionFailures();
+            }
+        };
+    }
+
+    public static Matcher<EventCollector> hasNoFailure() {
+        return hasNumberOfFailures(0);
+    }
+
+    public static Matcher<EventCollector> hasSingleFailure() {
+        return hasNumberOfFailures(1);
+    }
+
+    public static Matcher<EventCollector> hasSingleFailureWithMessage(
+            String message) {
+        return hasSingleFailureWithMessage(equalTo(message));
+    }
+
+    public static Matcher<EventCollector> hasSingleFailureWithMessage(
+            final Matcher<String> messageMatcher) {
+        return new SingleFailureMatcher(messageMatcher, "failure") {
+            @Override
+            List<Failure> getFailures(EventCollector collector) {
+                return collector.getFailures();
+            }
+        };
+    }
+
+    public static Matcher<EventCollector> hasNumberOfFailures(
+            int numberOfFailures) {
+        return new CountMatcher(numberOfFailures, "failures") {
+            @Override
+            Collection<?> getItems(EventCollector collector) {
+                return collector.getFailures();
+            }
+        };
+    }
+
+    public static Matcher<EventCollector> hasNumberOfTestRunsFinished(
+            int numberOfTestRuns) {
+        return new CountMatcher(numberOfTestRuns, "test runs finished") {
+            @Override
+            Collection<?> getItems(EventCollector collector) {
+                return collector.getTestRunsFinished();
+            }
+        };
+    }
+
+    public static Matcher<EventCollector> hasNumberOfTestRunsStarted(
+            int numberOfTestRuns) {
+        return new CountMatcher(numberOfTestRuns, "test runs started") {
+            @Override
+            Collection<?> getItems(EventCollector collector) {
+                return collector.getTestRunsStarted();
+            }
+        };
+    }
+
+    public static Matcher<EventCollector> hasNumberOfTestSuitesFinished(
+            int numberOfTestSuites) {
+        return new CountMatcher(numberOfTestSuites, "test suites finished") {
+            @Override
+            Collection<?> getItems(EventCollector collector) {
+                return collector.getTestSuitesFinished();
+            }
+        };
+    }
+
+    public static Matcher<EventCollector> hasNumberOfTestSuiteStarted(
+            int numberOfTestSuites) {
+        return new CountMatcher(numberOfTestSuites, "test suites started") {
+            @Override
+            Collection<?> getItems(EventCollector collector) {
+                return collector.getTestSuitesStarted();
+            }
+        };
+    }
+
+    public static Matcher<EventCollector> hasNumberOfTestsFinished(
+            int numberOfTests) {
+        return new CountMatcher(numberOfTests, "tests finished") {
+            @Override
+            Collection<?> getItems(EventCollector collector) {
+                return collector.getTestsFinished();
+            }
+        };
+    }
+
+    public static Matcher<EventCollector> hasTestFinished(
+            final Class<?> testClass) {
+        return new TestMatcher(testClass, "finished") {
+            @Override
+            List<Description> getTests(EventCollector collector) {
+                return collector.getTestsFinished();
+            }
+        };
+    }
+
+    public static Matcher<EventCollector> hasNumberOfTestsIgnored(
+            int numberOfTests) {
+        return new CountMatcher(numberOfTests, "tests ignored") {
+            @Override
+            Collection<?> getItems(EventCollector collector) {
+                return collector.getTestsIgnored();
+            }
+        };
+    }
+
+    public static Matcher<EventCollector> hasTestIgnored(
+            final Class<?> testClass) {
+        return new TestMatcher(testClass, "ignored") {
+            @Override
+            List<Description> getTests(EventCollector collector) {
+                return collector.getTestsIgnored();
+            }
+        };
+    }
+
+    public static Matcher<EventCollector> hasNumberOfTestsStarted(
+            int numberOfTests) {
+        return new CountMatcher(numberOfTests, "tests started") {
+            @Override
+            Collection<?> getItems(EventCollector collector) {
+                return collector.getTestsStarted();
+            }
+        };
+    }
+
+    public static Matcher<EventCollector> hasTestStarted(
+            final Class<?> testClass) {
+        return new TestMatcher(testClass, "started") {
+            @Override
+            List<Description> getTests(EventCollector collector) {
+                return collector.getTestsStarted();
+            }
+        };
+    }
+
+    private abstract static class CountMatcher extends
+            TypeSafeMatcher<EventCollector> {
+        private final int count;
+
+        private final String name;
+
+        CountMatcher(int count, String name) {
+            this.count = count;
+            this.name = name;
+        }
+
+        abstract Collection<?> getItems(EventCollector collector);
+
+        @Override
+        public boolean matchesSafely(EventCollector collector) {
+            return getItems(collector).size() == count;
+        }
+
+        public void describeTo(org.hamcrest.Description description) {
+            appendMessage(description, count);
+        }
+
+        @Override
+        protected void describeMismatchSafely(EventCollector collector,
+                org.hamcrest.Description description) {
+            appendMessage(description, getItems(collector).size());
+        }
+
+        private void appendMessage(org.hamcrest.Description description,
+                int countForMessage) {
+            description.appendText("has ");
+            description.appendValue(countForMessage);
+            description.appendText(" ");
+            description.appendText(name);
+        }
+    }
+
+    private abstract static class SingleFailureMatcher extends
+            TypeSafeMatcher<EventCollector> {
+        private final CountMatcher countMatcher;
+
+        private final Matcher<String> messageMatcher;
+
+        private final String name;
+
+        SingleFailureMatcher(Matcher<String> messageMatcher, String name) {
+            this.countMatcher = new CountMatcher(1, name) {
+                @Override
+                Collection<?> getItems(EventCollector collector) {
+                    return getFailures(collector);
+                }
+            };
+            this.messageMatcher = messageMatcher;
+            this.name = name;
+        }
+
+        abstract List<Failure> getFailures(EventCollector collector);
+
+        @Override
+        public boolean matchesSafely(EventCollector collector) {
+            return countMatcher.matches(collector)
+                    && messageMatcher.matches(getFailures(collector).get(0)
+                            .getMessage());
+        }
+
+        public void describeTo(org.hamcrest.Description description) {
+            description.appendText("has single ");
+            description.appendText(name);
+            description.appendText(" with message ");
+            messageMatcher.describeTo(description);
+        }
+
+        @Override
+        protected void describeMismatchSafely(EventCollector collector,
+                org.hamcrest.Description description) {
+            description.appendText("was ");
+            countMatcher.describeMismatch(collector, description);
+            description.appendText(": ");
+            boolean first = true;
+            for (Failure f : getFailures(collector)) {
+                if (!first) {
+                    description.appendText(" ,");
+                }
+                description.appendText("'");
+                description.appendText(f.getMessage());
+                description.appendText("'");
+                first = false;
+            }
+        }
+    }
+
+    private abstract static class TestMatcher extends
+            TypeSafeMatcher<EventCollector> {
+        private final Class<?> testClass;
+
+        private final String name;
+
+        TestMatcher(Class<?> testClass, String name) {
+            this.testClass = testClass;
+            this.name = name;
+        }
+
+        abstract List<Description> getTests(EventCollector collector);
+
+        @Override
+        public boolean matchesSafely(EventCollector collector) {
+            for (Description description : getTests(collector)) {
+                if (testClass.getName().equals(description.getClassName())) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public void describeTo(org.hamcrest.Description description) {
+            description.appendText("has test ");
+            description.appendValue(testClass);
+            description.appendText(" ");
+            description.appendText(name);
+        }
+
+        @Override
+        protected void describeMismatchSafely(EventCollector collector,
+                org.hamcrest.Description description) {
+            List<Description> tests = getTests(collector);
+            if (tests.isEmpty()) {
+                description.appendText("has no test");
+            } else {
+                description.appendText("has tests ");
+                boolean first = true;
+                for (Description test : getTests(collector)) {
+                    if (!first) {
+                        description.appendText(" ,");
+                    }
+                    description.appendValue(test.getClassName());
+                    first = false;
+                }
+            }
+            description.appendText(" ");
+            description.appendText(name);
+        }
+    }
+}
diff --git a/src/test/java/org/junit/testsupport/EventCollectorMatchersTest.java b/src/test/java/org/junit/testsupport/EventCollectorMatchersTest.java
new file mode 100644
index 000000000000..5183a61c7f23
--- /dev/null
+++ b/src/test/java/org/junit/testsupport/EventCollectorMatchersTest.java
@@ -0,0 +1,63 @@
+package org.junit.testsupport;
+
+import org.hamcrest.Matcher;
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.Result;
+import org.junit.runner.RunWith;
+import org.junit.runner.notification.Failure;
+import org.junit.runners.Parameterized;
+
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.runners.Parameterized.Parameter;
+import static org.junit.runners.Parameterized.Parameters;
+import static org.junit.testsupport.EventCollectorMatchers.everyTestRunSuccessful;
+import static org.junit.testsupport.EventCollectorMatchers.hasNoAssumptionFailure;
+import static org.junit.testsupport.EventCollectorMatchers.hasSingleAssumptionFailure;
+
+@RunWith(Parameterized.class)
+public class EventCollectorMatchersTest {
+    private static final Description DUMMY_DESCRIPTION = Description.EMPTY;
+
+    private static final Failure DUMMY_FAILURE = new Failure(null, new RuntimeException("dummy message"));
+
+    private static final Result DUMMY_RESULT = new Result();
+
+    private static final EventCollector COLLECTOR_WITH_NO_EVENTS = new EventCollector();
+
+    private static final EventCollector COLLECTOR_WITH_SINGLE_FAILURE = new EventCollector() {{
+        testFailure(DUMMY_FAILURE);
+    }};
+
+    private static final EventCollector COLLECTOR_WITH_SINGLE_ASSUMPTION_FAILURE = new EventCollector() {{
+        testAssumptionFailure(DUMMY_FAILURE);
+    }};
+
+    @Parameters(name = "{0}")
+    public static Object[][] data() {
+        return new Object[][] {
+                {"everyTestRunSuccessful() matches if no failures are reported", COLLECTOR_WITH_NO_EVENTS, everyTestRunSuccessful()},
+                {"everyTestRunSuccessful() does not match if failure is reported", COLLECTOR_WITH_SINGLE_FAILURE, not(everyTestRunSuccessful())},
+                {"everyTestRunSuccessful() does not match if assumption failure is reported", COLLECTOR_WITH_SINGLE_ASSUMPTION_FAILURE, not(everyTestRunSuccessful())},
+                {"hasNoAssumptionFailure() matches if no assumption failure is reported", COLLECTOR_WITH_NO_EVENTS, hasNoAssumptionFailure()},
+                {"hasNoAssumptionFailure() does not match if assumption failure is reported", COLLECTOR_WITH_SINGLE_ASSUMPTION_FAILURE, not(hasNoAssumptionFailure())},
+                {"hasSingleAssumptionFailure() matches if single assumption failure is reported", COLLECTOR_WITH_SINGLE_ASSUMPTION_FAILURE, hasSingleAssumptionFailure()},
+                {"hasSingleAssumptionFailure() does not match if no assumption failure is reported", COLLECTOR_WITH_NO_EVENTS, not(hasSingleAssumptionFailure())}
+        };
+    }
+
+    @Parameter(0)
+    public String testName; //must be assigned. Otherwise the Parameterized runner fails.
+
+    @Parameter(1)
+    public EventCollector collector;
+
+    @Parameter(2)
+    public Matcher<EventCollector> matcher;
+
+    @Test
+    public void matchesCollector() {
+        assertThat(collector, matcher);
+    }
+}
diff --git a/src/test/java/org/junit/testsupport/EventCollectorTest.java b/src/test/java/org/junit/testsupport/EventCollectorTest.java
new file mode 100644
index 000000000000..b92d09d07794
--- /dev/null
+++ b/src/test/java/org/junit/testsupport/EventCollectorTest.java
@@ -0,0 +1,139 @@
+package org.junit.testsupport;
+
+import static java.util.Collections.singletonList;
+import static org.junit.Assert.assertEquals;
+import static org.junit.rules.ExpectedException.none;
+
+import java.util.List;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.Description;
+import org.junit.runner.Result;
+import org.junit.runner.notification.Failure;
+
+public class EventCollectorTest {
+    private static final Description DUMMY_DESCRIPTION = Description.EMPTY;
+    private static final Failure DUMMY_FAILURE = new Failure(null, null);
+    private static final Result DUMMY_RESULT = new Result();
+
+    @Rule
+    public final ExpectedException thrown = none();
+
+    private final EventCollector collector = new EventCollector();
+
+    @Test
+    public void collectsTestRunsStarted() {
+        collector.testRunStarted(DUMMY_DESCRIPTION);
+        assertEquals(singletonList(DUMMY_DESCRIPTION), collector.getTestRunsStarted());
+    }
+
+    @Test
+    public void returnsUnmodifiableListOfTestRunsStarted() {
+        assertNoDescriptionCanBeAddedToList(collector.getTestRunsStarted());
+    }
+
+    @Test
+    public void collectsTestRunsFinished() {
+        collector.testRunFinished(DUMMY_RESULT);
+        assertEquals(singletonList(DUMMY_RESULT), collector.getTestRunsFinished());
+    }
+
+    @Test
+    public void returnsUnmodifiableListOfTestRunsFinished() {
+        assertNoResultCanBeAddedToList(collector.getTestRunsFinished());
+    }
+
+    @Test
+    public void collectsTestSuitesStarted() {
+        collector.testSuiteStarted(DUMMY_DESCRIPTION);
+        assertEquals(singletonList(DUMMY_DESCRIPTION), collector.getTestSuitesStarted());
+    }
+
+    @Test
+    public void returnsUnmodifiableListOfTestSuitesStarted() {
+        assertNoDescriptionCanBeAddedToList(collector.getTestSuitesStarted());
+    }
+
+    @Test
+    public void collectsTestSuitesFinished() {
+        collector.testSuiteFinished(DUMMY_DESCRIPTION);
+        assertEquals(singletonList(DUMMY_DESCRIPTION), collector.getTestSuitesFinished());
+    }
+
+    @Test
+    public void returnsUnmodifiableListOfTestSuitesFinished() {
+        assertNoDescriptionCanBeAddedToList(collector.getTestSuitesFinished());
+    }
+
+    @Test
+    public void collectsTestsStarted() {
+        collector.testStarted(DUMMY_DESCRIPTION);
+        assertEquals(singletonList(DUMMY_DESCRIPTION), collector.getTestsStarted());
+    }
+
+    @Test
+    public void returnsUnmodifiableListOfTestsStarted() {
+        assertNoDescriptionCanBeAddedToList(collector.getTestsStarted());
+    }
+
+    @Test
+    public void collectsTestsFinished() {
+        collector.testFinished(DUMMY_DESCRIPTION);
+        assertEquals(singletonList(DUMMY_DESCRIPTION), collector.getTestsFinished());
+    }
+
+    @Test
+    public void returnsUnmodifiableListOfTestsFinished() {
+        assertNoDescriptionCanBeAddedToList(collector.getTestsFinished());
+    }
+
+    @Test
+    public void collectsFailures() {
+        collector.testFailure(DUMMY_FAILURE);
+        assertEquals(singletonList(DUMMY_FAILURE), collector.getFailures());
+    }
+
+    @Test
+    public void returnsUnmodifiableListOfFailures() {
+        assertNoFailureCanBeAddedToList(collector.getFailures());
+    }
+
+    @Test
+    public void collectsAssumptionFailures() {
+        collector.testAssumptionFailure(DUMMY_FAILURE);
+        assertEquals(singletonList(DUMMY_FAILURE), collector.getAssumptionFailures());
+    }
+
+    @Test
+    public void returnsUnmodifiableListOfAssumptionFailures() {
+        assertNoFailureCanBeAddedToList(collector.getAssumptionFailures());
+    }
+
+    @Test
+    public void collectsTestsIgnored() {
+        collector.testIgnored(DUMMY_DESCRIPTION);
+        assertEquals(singletonList(DUMMY_DESCRIPTION), collector.getTestsIgnored());
+    }
+
+    @Test
+    public void returnsUnmodifiableListOfTestsIgnored() {
+        assertNoDescriptionCanBeAddedToList(collector.getTestsIgnored());
+    }
+
+    private void assertNoDescriptionCanBeAddedToList(List<Description> list) {
+        thrown.expect(Exception.class);
+        list.add(DUMMY_DESCRIPTION);
+    }
+
+    private void assertNoFailureCanBeAddedToList(List<Failure> list) {
+        thrown.expect(Exception.class);
+        list.add(DUMMY_FAILURE);
+    }
+
+    private void assertNoResultCanBeAddedToList(List<Result> list) {
+        thrown.expect(Exception.class);
+        list.add(DUMMY_RESULT);
+    }
+}