diff options
author | Paul Duffin <paulduffin@google.com> | 2017-01-23 14:41:00 +0000 |
---|---|---|
committer | Paul Duffin <paulduffin@google.com> | 2017-01-23 15:34:33 +0000 |
commit | facdecc8e055d722b8c0e0c8fbf8a8644e7fec57 (patch) | |
tree | 9930cc8a22092529188aaa026b3af0681c8c3023 /src/main/java/org | |
parent | 7323bf635ef4f8c3d3f40e4ee1e4c50652e782c8 (diff) | |
download | junit-facdecc8e055d722b8c0e0c8fbf8a8644e7fec57.tar.gz |
Revert "Revert matchers back to 4.10 to compile against Hamcrest 1.1"
This reverts commit 86f323b2a73c9c250fbe25b828435aa936704ab3.
Bug: 30946317
Test: make checkbuild
Change-Id: Ie72fec656d01f24a1724d33a35ceecdb57aaba57
Diffstat (limited to 'src/main/java/org')
13 files changed, 564 insertions, 393 deletions
diff --git a/src/main/java/org/junit/Assume.java b/src/main/java/org/junit/Assume.java index c96ff99..b7687f7 100644 --- a/src/main/java/org/junit/Assume.java +++ b/src/main/java/org/junit/Assume.java @@ -1,12 +1,12 @@ package org.junit; import static java.util.Arrays.asList; +import static org.hamcrest.CoreMatchers.everyItem; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; import org.hamcrest.Matcher; -import org.junit.internal.matchers.Each; /** * A set of methods useful for stating assumptions about the conditions in which a test is meaningful. @@ -66,13 +66,12 @@ public class Assume { assumeTrue(message, !b); } - /** - * If called with one or more null elements in <code>objects</code>, the test will halt and be ignored. - * @param objects - */ - public static void assumeNotNull(Object... objects) { - assumeThat(asList(objects), Each.each(notNullValue())); - } + /** + * If called with one or more null elements in <code>objects</code>, the test will halt and be ignored. + */ + public static void assumeNotNull(Object... objects) { + assumeThat(asList(objects), everyItem(notNullValue())); + } /** * Call to assume that <code>actual</code> satisfies the condition specified by <code>matcher</code>. diff --git a/src/main/java/org/junit/internal/matchers/CombinableMatcher.java b/src/main/java/org/junit/internal/matchers/CombinableMatcher.java deleted file mode 100644 index e9e6947..0000000 --- a/src/main/java/org/junit/internal/matchers/CombinableMatcher.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.junit.internal.matchers; - -import static org.hamcrest.CoreMatchers.allOf; -import static org.hamcrest.CoreMatchers.anyOf; -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; -import org.hamcrest.Matcher; - -public class CombinableMatcher<T> extends BaseMatcher<T> { - - private final Matcher<? extends T> fMatcher; - - public CombinableMatcher(Matcher<? extends T> matcher) { - fMatcher= matcher; - } - - public boolean matches(Object item) { - return fMatcher.matches(item); - } - - public void describeTo(Description description) { - description.appendDescriptionOf(fMatcher); - } - - @SuppressWarnings("unchecked") - public CombinableMatcher<T> and(Matcher<? extends T> matcher) { - return new CombinableMatcher<T>(allOf(matcher, fMatcher)); - } - - @SuppressWarnings("unchecked") - public CombinableMatcher<T> or(Matcher<? extends T> matcher) { - return new CombinableMatcher<T>(anyOf(matcher, fMatcher)); - } -}
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/matchers/Each.java b/src/main/java/org/junit/internal/matchers/Each.java deleted file mode 100644 index 527db3b..0000000 --- a/src/main/java/org/junit/internal/matchers/Each.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.junit.internal.matchers; - -import static org.hamcrest.CoreMatchers.not; -import static org.junit.internal.matchers.IsCollectionContaining.hasItem; -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; -import org.hamcrest.Matcher; - -public class Each { - public static <T> Matcher<Iterable<T>> each(final Matcher<T> individual) { - final Matcher<Iterable<T>> allItemsAre = not(hasItem(not(individual))); - - return new BaseMatcher<Iterable<T>>() { - public boolean matches(Object item) { - return allItemsAre.matches(item); - } - - public void describeTo(Description description) { - description.appendText("each "); - individual.describeTo(description); - } - }; - } -} diff --git a/src/main/java/org/junit/internal/matchers/IsCollectionContaining.java b/src/main/java/org/junit/internal/matchers/IsCollectionContaining.java deleted file mode 100644 index 4436a83..0000000 --- a/src/main/java/org/junit/internal/matchers/IsCollectionContaining.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.junit.internal.matchers; - -import static org.hamcrest.core.AllOf.allOf; -import static org.hamcrest.core.IsEqual.equalTo; - -import java.util.ArrayList; -import java.util.Collection; - -import org.hamcrest.Description; -import org.hamcrest.Factory; -import org.hamcrest.Matcher; - -// Copied (hopefully temporarily) from hamcrest-library -public class IsCollectionContaining<T> extends TypeSafeMatcher<Iterable<T>> { - private final Matcher<? extends T> elementMatcher; - - public IsCollectionContaining(Matcher<? extends T> elementMatcher) { - this.elementMatcher = elementMatcher; - } - - @Override - public boolean matchesSafely(Iterable<T> collection) { - for (T item : collection) { - if (elementMatcher.matches(item)){ - return true; - } - } - return false; - } - - public void describeTo(Description description) { - description - .appendText("a collection containing ") - .appendDescriptionOf(elementMatcher); - } - - @Factory - public static <T> Matcher<Iterable<T>> hasItem(Matcher<? extends T> elementMatcher) { - return new IsCollectionContaining<T>(elementMatcher); - } - - @Factory - public static <T> Matcher<Iterable<T>> hasItem(T element) { - return hasItem(equalTo(element)); - } - - @Factory - public static <T> Matcher<Iterable<T>> hasItems(Matcher<? extends T>... elementMatchers) { - Collection<Matcher<? extends Iterable<T>>> all - = new ArrayList<Matcher<? extends Iterable<T>>>(elementMatchers.length); - for (Matcher<? extends T> elementMatcher : elementMatchers) { - all.add(hasItem(elementMatcher)); - } - return allOf(all); - } - - @Factory - public static <T> Matcher<Iterable<T>> hasItems(T... elements) { - Collection<Matcher<? extends Iterable<T>>> all - = new ArrayList<Matcher<? extends Iterable<T>>>(elements.length); - for (T element : elements) { - all.add(hasItem(element)); - } - return allOf(all); - } - -} diff --git a/src/main/java/org/junit/internal/matchers/StacktracePrintingMatcher.java b/src/main/java/org/junit/internal/matchers/StacktracePrintingMatcher.java new file mode 100644 index 0000000..5d45ba3 --- /dev/null +++ b/src/main/java/org/junit/internal/matchers/StacktracePrintingMatcher.java @@ -0,0 +1,56 @@ +package org.junit.internal.matchers; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.hamcrest.Description; +import org.hamcrest.Factory; +import org.hamcrest.Matcher; + +/** + * A matcher that delegates to throwableMatcher and in addition appends the + * stacktrace of the actual Throwable in case of a mismatch. + */ +public class StacktracePrintingMatcher<T extends Throwable> extends + org.hamcrest.TypeSafeMatcher<T> { + + private final Matcher<T> throwableMatcher; + + public StacktracePrintingMatcher(Matcher<T> throwableMatcher) { + this.throwableMatcher = throwableMatcher; + } + + public void describeTo(Description description) { + throwableMatcher.describeTo(description); + } + + @Override + protected boolean matchesSafely(T item) { + return throwableMatcher.matches(item); + } + + @Override + protected void describeMismatchSafely(T item, Description description) { + throwableMatcher.describeMismatch(item, description); + description.appendText("\nStacktrace was: "); + description.appendText(readStacktrace(item)); + } + + private String readStacktrace(Throwable throwable) { + StringWriter stringWriter = new StringWriter(); + throwable.printStackTrace(new PrintWriter(stringWriter)); + return stringWriter.toString(); + } + + @Factory + public static <T extends Throwable> Matcher<T> isThrowable( + Matcher<T> throwableMatcher) { + return new StacktracePrintingMatcher<T>(throwableMatcher); + } + + @Factory + public static <T extends Exception> Matcher<T> isException( + Matcher<T> exceptionMatcher) { + return new StacktracePrintingMatcher<T>(exceptionMatcher); + } +} diff --git a/src/main/java/org/junit/internal/matchers/StringContains.java b/src/main/java/org/junit/internal/matchers/StringContains.java deleted file mode 100644 index e5f5334..0000000 --- a/src/main/java/org/junit/internal/matchers/StringContains.java +++ /dev/null @@ -1,31 +0,0 @@ -/* Copyright (c) 2000-2006 hamcrest.org - */ -package org.junit.internal.matchers; - -import org.hamcrest.Factory; -import org.hamcrest.Matcher; - -/** - * Tests if the argument is a string that contains a substring. - */ -public class StringContains extends SubstringMatcher { - public StringContains(String substring) { - super(substring); - } - - @Override - protected boolean evalSubstringOf(String s) { - return s.indexOf(substring) >= 0; - } - - @Override - protected String relationship() { - return "containing"; - } - - @Factory - public static Matcher<String> containsString(String substring) { - return new StringContains(substring); - } - -}
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/matchers/SubstringMatcher.java b/src/main/java/org/junit/internal/matchers/SubstringMatcher.java deleted file mode 100644 index 1c65240..0000000 --- a/src/main/java/org/junit/internal/matchers/SubstringMatcher.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.junit.internal.matchers; - -import org.hamcrest.Description; - -public abstract class SubstringMatcher extends TypeSafeMatcher<String> { - - protected final String substring; - - protected SubstringMatcher(final String substring) { - this.substring = substring; - } - - @Override - public boolean matchesSafely(String item) { - return evalSubstringOf(item); - } - - public void describeTo(Description description) { - description.appendText("a string ") - .appendText(relationship()) - .appendText(" ") - .appendValue(substring); - } - - protected abstract boolean evalSubstringOf(String string); - - protected abstract String relationship(); -}
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/matchers/ThrowableCauseMatcher.java b/src/main/java/org/junit/internal/matchers/ThrowableCauseMatcher.java new file mode 100644 index 0000000..22ce8bd --- /dev/null +++ b/src/main/java/org/junit/internal/matchers/ThrowableCauseMatcher.java @@ -0,0 +1,50 @@ +package org.junit.internal.matchers; + +import org.hamcrest.Description; +import org.hamcrest.Factory; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +/** + * A matcher that applies a delegate matcher to the cause of the current Throwable, returning the result of that + * match. + * + * @param <T> the type of the throwable being matched + */ +public class ThrowableCauseMatcher<T extends Throwable> extends + TypeSafeMatcher<T> { + + private final Matcher<? extends Throwable> causeMatcher; + + public ThrowableCauseMatcher(Matcher<? extends Throwable> causeMatcher) { + this.causeMatcher = causeMatcher; + } + + public void describeTo(Description description) { + description.appendText("exception with cause "); + description.appendDescriptionOf(causeMatcher); + } + + @Override + protected boolean matchesSafely(T item) { + return causeMatcher.matches(item.getCause()); + } + + @Override + protected void describeMismatchSafely(T item, Description description) { + description.appendText("cause "); + causeMatcher.describeMismatch(item.getCause(), description); + } + + /** + * Returns a matcher that verifies that the outer exception has a cause for which the supplied matcher + * evaluates to true. + * + * @param matcher to apply to the cause of the outer exception + * @param <T> type of the outer exception + */ + @Factory + public static <T extends Throwable> Matcher<T> hasCause(final Matcher<? extends Throwable> matcher) { + return new ThrowableCauseMatcher<T>(matcher); + } +}
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/matchers/ThrowableMessageMatcher.java b/src/main/java/org/junit/internal/matchers/ThrowableMessageMatcher.java new file mode 100644 index 0000000..74386a8 --- /dev/null +++ b/src/main/java/org/junit/internal/matchers/ThrowableMessageMatcher.java @@ -0,0 +1,37 @@ +package org.junit.internal.matchers; + +import org.hamcrest.Description; +import org.hamcrest.Factory; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +public class ThrowableMessageMatcher<T extends Throwable> extends + TypeSafeMatcher<T> { + + private final Matcher<String> matcher; + + public ThrowableMessageMatcher(Matcher<String> matcher) { + this.matcher = matcher; + } + + public void describeTo(Description description) { + description.appendText("exception with message "); + description.appendDescriptionOf(matcher); + } + + @Override + protected boolean matchesSafely(T item) { + return matcher.matches(item.getMessage()); + } + + @Override + protected void describeMismatchSafely(T item, Description description) { + description.appendText("message "); + matcher.describeMismatch(item.getMessage(), description); + } + + @Factory + public static <T extends Throwable> Matcher<T> hasMessage(final Matcher<String> matcher) { + return new ThrowableMessageMatcher<T>(matcher); + } +}
\ No newline at end of file diff --git a/src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java b/src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java index 794a174..4e2cc12 100644 --- a/src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java +++ b/src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java @@ -3,13 +3,16 @@ package org.junit.internal.matchers; import java.lang.reflect.Method; import org.hamcrest.BaseMatcher; +import org.junit.internal.MethodSorter; /** * Convenient base class for Matchers that require a non-null value of a specific type. * This simply implements the null check, checks the type and then casts. * * @author Joe Walnes + * @deprecated Please use {@link org.hamcrest.TypeSafeMatcher}. */ +@Deprecated public abstract class TypeSafeMatcher<T> extends BaseMatcher<T> { private Class<?> expectedType; @@ -23,27 +26,27 @@ public abstract class TypeSafeMatcher<T> extends BaseMatcher<T> { protected TypeSafeMatcher() { expectedType = findExpectedType(getClass()); } - + private static Class<?> findExpectedType(Class<?> fromClass) { for (Class<?> c = fromClass; c != Object.class; c = c.getSuperclass()) { - for (Method method : c.getDeclaredMethods()) { + for (Method method : MethodSorter.getDeclaredMethods(c)) { if (isMatchesSafelyMethod(method)) { return method.getParameterTypes()[0]; } } } - + throw new Error("Cannot determine correct type for matchesSafely() method."); } - + private static boolean isMatchesSafelyMethod(Method method) { - return method.getName().equals("matchesSafely") - && method.getParameterTypes().length == 1 - && !method.isSynthetic(); + return method.getName().equals("matchesSafely") + && method.getParameterTypes().length == 1 + && !method.isSynthetic(); } - + protected TypeSafeMatcher(Class<T> expectedType) { - this.expectedType = expectedType; + this.expectedType = expectedType; } /** diff --git a/src/main/java/org/junit/matchers/JUnitMatchers.java b/src/main/java/org/junit/matchers/JUnitMatchers.java index 847a347..13407cc 100644 --- a/src/main/java/org/junit/matchers/JUnitMatchers.java +++ b/src/main/java/org/junit/matchers/JUnitMatchers.java @@ -1,83 +1,113 @@ package org.junit.matchers; +import org.hamcrest.CoreMatchers; import org.hamcrest.Matcher; -import org.junit.internal.matchers.CombinableMatcher; -import org.junit.internal.matchers.Each; -import org.junit.internal.matchers.IsCollectionContaining; -import org.junit.internal.matchers.StringContains; +import org.hamcrest.core.CombinableMatcher.CombinableBothMatcher; +import org.hamcrest.core.CombinableMatcher.CombinableEitherMatcher; +import org.junit.internal.matchers.StacktracePrintingMatcher; /** * Convenience import class: these are useful matchers for use with the assertThat method, but they are * not currently included in the basic CoreMatchers class from hamcrest. + * + * @since 4.4 */ public class JUnitMatchers { - /** - * @param element - * @return A matcher matching any collection containing element - */ - public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItem(T element) { - return IsCollectionContaining.hasItem(element); - } + /** + * @return A matcher matching any collection containing element + * @deprecated Please use {@link CoreMatchers#hasItem(Object)} instead. + */ + @Deprecated + public static <T> Matcher<Iterable<? super T>> hasItem(T element) { + return CoreMatchers.hasItem(element); + } - /** - * @param elementMatcher - * @return A matcher matching any collection containing an element matching elementMatcher - */ - public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItem(org.hamcrest.Matcher<? extends T> elementMatcher) { - return IsCollectionContaining.<T>hasItem(elementMatcher); - } + /** + * @return A matcher matching any collection containing an element matching elementMatcher + * @deprecated Please use {@link CoreMatchers#hasItem(Matcher)} instead. + */ + @Deprecated + public static <T> Matcher<Iterable<? super T>> hasItem(Matcher<? super T> elementMatcher) { + return CoreMatchers.<T>hasItem(elementMatcher); + } - /** - * @param elements - * @return A matcher matching any collection containing every element in elements - */ - public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItems(T... elements) { - return IsCollectionContaining.hasItems(elements); - } + /** + * @return A matcher matching any collection containing every element in elements + * @deprecated Please use {@link CoreMatchers#hasItems(Object...)} instead. + */ + @Deprecated + public static <T> Matcher<Iterable<T>> hasItems(T... elements) { + return CoreMatchers.hasItems(elements); + } - /** - * @param elementMatchers - * @return A matcher matching any collection containing at least one element that matches - * each matcher in elementMatcher (this may be one element matching all matchers, - * or different elements matching each matcher) - */ - public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItems(org.hamcrest.Matcher<? extends T>... elementMatchers) { - return IsCollectionContaining.<T>hasItems(elementMatchers); - } + /** + * @return A matcher matching any collection containing at least one element that matches + * each matcher in elementMatcher (this may be one element matching all matchers, + * or different elements matching each matcher) + * @deprecated Please use {@link CoreMatchers#hasItems(Matcher...)} instead. + */ + @Deprecated + public static <T> Matcher<Iterable<T>> hasItems(Matcher<? super T>... elementMatchers) { + return CoreMatchers.hasItems(elementMatchers); + } - /** - * @param elementMatcher - * @return A matcher matching any collection in which every element matches elementMatcher - */ - public static <T> Matcher<Iterable<T>> everyItem(final Matcher<T> elementMatcher) { - return Each.each(elementMatcher); - } + /** + * @return A matcher matching any collection in which every element matches elementMatcher + * @deprecated Please use {@link CoreMatchers#everyItem(Matcher)} instead. + */ + @Deprecated + public static <T> Matcher<Iterable<T>> everyItem(final Matcher<T> elementMatcher) { + return CoreMatchers.everyItem(elementMatcher); + } - /** - * @param substring - * @return a matcher matching any string that contains substring - */ - public static org.hamcrest.Matcher<java.lang.String> containsString(java.lang.String substring) { - return StringContains.containsString(substring); - } - - /** - * This is useful for fluently combining matchers that must both pass. For example: - * <pre> - * assertThat(string, both(containsString("a")).and(containsString("b"))); - * </pre> - */ - public static <T> CombinableMatcher<T> both(Matcher<T> matcher) { - return new CombinableMatcher<T>(matcher); - } - - /** - * This is useful for fluently combining matchers where either may pass, for example: - * <pre> - * assertThat(string, either(containsString("a")).or(containsString("b"))); - * </pre> - */ - public static <T> CombinableMatcher<T> either(Matcher<T> matcher) { - return new CombinableMatcher<T>(matcher); - } + /** + * @return a matcher matching any string that contains substring + * @deprecated Please use {@link CoreMatchers#containsString(String)} instead. + */ + @Deprecated + public static Matcher<java.lang.String> containsString(java.lang.String substring) { + return CoreMatchers.containsString(substring); + } + + /** + * This is useful for fluently combining matchers that must both pass. For example: + * <pre> + * assertThat(string, both(containsString("a")).and(containsString("b"))); + * </pre> + * + * @deprecated Please use {@link CoreMatchers#both(Matcher)} instead. + */ + @Deprecated + public static <T> CombinableBothMatcher<T> both(Matcher<? super T> matcher) { + return CoreMatchers.both(matcher); + } + + /** + * This is useful for fluently combining matchers where either may pass, for example: + * <pre> + * assertThat(string, either(containsString("a")).or(containsString("b"))); + * </pre> + * + * @deprecated Please use {@link CoreMatchers#either(Matcher)} instead. + */ + @Deprecated + public static <T> CombinableEitherMatcher<T> either(Matcher<? super T> matcher) { + return CoreMatchers.either(matcher); + } + + /** + * @return A matcher that delegates to throwableMatcher and in addition + * appends the stacktrace of the actual Throwable in case of a mismatch. + */ + public static <T extends Throwable> Matcher<T> isThrowable(Matcher<T> throwableMatcher) { + return StacktracePrintingMatcher.isThrowable(throwableMatcher); + } + + /** + * @return A matcher that delegates to exceptionMatcher and in addition + * appends the stacktrace of the actual Exception in case of a mismatch. + */ + public static <T extends Exception> Matcher<T> isException(Matcher<T> exceptionMatcher) { + return StacktracePrintingMatcher.isException(exceptionMatcher); + } } diff --git a/src/main/java/org/junit/rules/ExpectedException.java b/src/main/java/org/junit/rules/ExpectedException.java index bac2fba..4d61712 100644 --- a/src/main/java/org/junit/rules/ExpectedException.java +++ b/src/main/java/org/junit/rules/ExpectedException.java @@ -1,136 +1,270 @@ package org.junit.rules; +import static java.lang.String.format; +import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.instanceOf; -import static org.junit.matchers.JUnitMatchers.both; -import static org.junit.matchers.JUnitMatchers.containsString; -import org.hamcrest.Description; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.junit.internal.matchers.ThrowableCauseMatcher.hasCause; +import static org.junit.internal.matchers.ThrowableMessageMatcher.hasMessage; + import org.hamcrest.Matcher; import org.hamcrest.StringDescription; -import org.junit.Assert; -import org.junit.internal.matchers.TypeSafeMatcher; +import org.junit.AssumptionViolatedException; import org.junit.runners.model.Statement; /** - * The ExpectedException Rule allows in-test specification of expected exception - * types and messages: - * - * <pre> - * // These tests all pass. - * public static class HasExpectedException { - * @Rule - * public ExpectedException thrown= ExpectedException.none(); - * - * @Test - * public void throwsNothing() { - * // no exception expected, none thrown: passes. - * } + * The {@code ExpectedException} rule allows you to verify that your code + * throws a specific exception. + * + * <h3>Usage</h3> + * + * <pre> public class SimpleExpectedExceptionTest { + * @Rule + * public ExpectedException thrown= ExpectedException.none(); + * + * @Test + * public void throwsNothing() { + * // no exception expected, none thrown: passes. + * } + * + * @Test + * public void throwsExceptionWithSpecificType() { + * thrown.expect(NullPointerException.class); + * throw new NullPointerException(); + * } + * }</pre> * - * @Test - * public void throwsNullPointerException() { - * thrown.expect(NullPointerException.class); - * throw new NullPointerException(); - * } - * - * @Test - * public void throwsNullPointerExceptionWithMessage() { - * thrown.expect(NullPointerException.class); - * thrown.expectMessage("happened?"); - * thrown.expectMessage(startsWith("What")); - * throw new NullPointerException("What happened?"); - * } - * } - * </pre> + * <p> + * You have to add the {@code ExpectedException} rule to your test. + * This doesn't affect your existing tests (see {@code throwsNothing()}). + * After specifiying the type of the expected exception your test is + * successful when such an exception is thrown and it fails if a + * different or no exception is thrown. + * + * <p> + * Instead of specifying the exception's type you can characterize the + * expected exception based on other criterias, too: + * + * <ul> + * <li>The exception's message contains a specific text: {@link #expectMessage(String)}</li> + * <li>The exception's message complies with a Hamcrest matcher: {@link #expectMessage(Matcher)}</li> + * <li>The exception's cause complies with a Hamcrest matcher: {@link #expectCause(Matcher)}</li> + * <li>The exception itself complies with a Hamcrest matcher: {@link #expect(Matcher)}</li> + * </ul> + * + * <p> + * You can combine any of the presented expect-methods. The test is + * successful if all specifications are met. + * <pre> @Test + * public void throwsException() { + * thrown.expect(NullPointerException.class); + * thrown.expectMessage("happened"); + * throw new NullPointerException("What happened?"); + * }</pre> + * + * <h3>AssumptionViolatedExceptions</h3> + * <p> + * JUnit uses {@link AssumptionViolatedException}s for indicating that a test + * provides no useful information. (See {@link org.junit.Assume} for more + * information.) You have to call {@code assume} methods before you set + * expectations of the {@code ExpectedException} rule. In this case the rule + * will not handle consume the exceptions and it can be handled by the + * framework. E.g. the following test is ignored by JUnit's default runner. + * + * <pre> @Test + * public void ignoredBecauseOfFailedAssumption() { + * assumeTrue(false); // throws AssumptionViolatedException + * thrown.expect(NullPointerException.class); + * }</pre> + * + * <h3>AssertionErrors</h3> + * + * <p> + * JUnit uses {@link AssertionError}s for indicating that a test is failing. You + * have to call {@code assert} methods before you set expectations of the + * {@code ExpectedException} rule, if they should be handled by the framework. + * E.g. the following test fails because of the {@code assertTrue} statement. + * + * <pre> @Test + * public void throwsUnhandled() { + * assertTrue(false); // throws AssertionError + * thrown.expect(NullPointerException.class); + * }</pre> + * + * <h3>Missing Exceptions</h3> + * <p> + * By default missing exceptions are reported with an error message + * like "Expected test to throw an instance of foo". You can configure a different + * message by means of {@link #reportMissingExceptionWithMessage(String)}. You + * can use a {@code %s} placeholder for the description of the expected + * exception. E.g. "Test doesn't throw %s." will fail with the error message + * "Test doesn't throw an instance of foo.". + * + * @since 4.7 */ public class ExpectedException implements TestRule { - /** - * @return a Rule that expects no exception to be thrown - * (identical to behavior without this Rule) - */ - public static ExpectedException none() { - return new ExpectedException(); - } - - private Matcher<Object> fMatcher= null; - - private ExpectedException() { - - } - - public Statement apply(Statement base, - org.junit.runner.Description description) { - return new ExpectedExceptionStatement(base); - } - - /** - * Adds {@code matcher} to the list of requirements for any thrown exception. - */ - // Should be able to remove this suppression in some brave new hamcrest world. - @SuppressWarnings("unchecked") - public void expect(Matcher<?> matcher) { - if (fMatcher == null) - fMatcher= (Matcher<Object>) matcher; - else - fMatcher= both(fMatcher).and(matcher); - } - - /** - * Adds to the list of requirements for any thrown exception that it - * should be an instance of {@code type} - */ - public void expect(Class<? extends Throwable> type) { - expect(instanceOf(type)); - } - - /** - * Adds to the list of requirements for any thrown exception that it - * should <em>contain</em> string {@code substring} - */ - public void expectMessage(String substring) { - expectMessage(containsString(substring)); - } - - /** - * Adds {@code matcher} to the list of requirements for the message - * returned from any thrown exception. - */ - public void expectMessage(Matcher<String> matcher) { - expect(hasMessage(matcher)); - } - - private class ExpectedExceptionStatement extends Statement { - private final Statement fNext; - - public ExpectedExceptionStatement(Statement base) { - fNext= base; - } - - @Override - public void evaluate() throws Throwable { - try { - fNext.evaluate(); - } catch (Throwable e) { - if (fMatcher == null) - throw e; - Assert.assertThat(e, fMatcher); - return; - } - if (fMatcher != null) - throw new AssertionError("Expected test to throw " - + StringDescription.toString(fMatcher)); - } - } - - private Matcher<Throwable> hasMessage(final Matcher<String> matcher) { - return new TypeSafeMatcher<Throwable>() { - public void describeTo(Description description) { - description.appendText("exception with message "); - description.appendDescriptionOf(matcher); - } - - @Override - public boolean matchesSafely(Throwable item) { - return matcher.matches(item.getMessage()); - } - }; - } + /** + * Returns a {@linkplain TestRule rule} that expects no exception to + * be thrown (identical to behavior without this rule). + */ + public static ExpectedException none() { + return new ExpectedException(); + } + + private final ExpectedExceptionMatcherBuilder matcherBuilder = new ExpectedExceptionMatcherBuilder(); + + private String missingExceptionMessage= "Expected test to throw %s"; + + private ExpectedException() { + } + + /** + * This method does nothing. Don't use it. + * @deprecated AssertionErrors are handled by default since JUnit 4.12. Just + * like in JUnit <= 4.10. + */ + @Deprecated + public ExpectedException handleAssertionErrors() { + return this; + } + + /** + * This method does nothing. Don't use it. + * @deprecated AssumptionViolatedExceptions are handled by default since + * JUnit 4.12. Just like in JUnit <= 4.10. + */ + @Deprecated + public ExpectedException handleAssumptionViolatedExceptions() { + return this; + } + + /** + * Specifies the failure message for tests that are expected to throw + * an exception but do not throw any. You can use a {@code %s} placeholder for + * the description of the expected exception. E.g. "Test doesn't throw %s." + * will fail with the error message + * "Test doesn't throw an instance of foo.". + * + * @param message exception detail message + * @return the rule itself + */ + public ExpectedException reportMissingExceptionWithMessage(String message) { + missingExceptionMessage = message; + return this; + } + + public Statement apply(Statement base, + org.junit.runner.Description description) { + return new ExpectedExceptionStatement(base); + } + + /** + * Verify that your code throws an exception that is matched by + * a Hamcrest matcher. + * <pre> @Test + * public void throwsExceptionThatCompliesWithMatcher() { + * NullPointerException e = new NullPointerException(); + * thrown.expect(is(e)); + * throw e; + * }</pre> + */ + public void expect(Matcher<?> matcher) { + matcherBuilder.add(matcher); + } + + /** + * Verify that your code throws an exception that is an + * instance of specific {@code type}. + * <pre> @Test + * public void throwsExceptionWithSpecificType() { + * thrown.expect(NullPointerException.class); + * throw new NullPointerException(); + * }</pre> + */ + public void expect(Class<? extends Throwable> type) { + expect(instanceOf(type)); + } + + /** + * Verify that your code throws an exception whose message contains + * a specific text. + * <pre> @Test + * public void throwsExceptionWhoseMessageContainsSpecificText() { + * thrown.expectMessage("happened"); + * throw new NullPointerException("What happened?"); + * }</pre> + */ + public void expectMessage(String substring) { + expectMessage(containsString(substring)); + } + + /** + * Verify that your code throws an exception whose message is matched + * by a Hamcrest matcher. + * <pre> @Test + * public void throwsExceptionWhoseMessageCompliesWithMatcher() { + * thrown.expectMessage(startsWith("What")); + * throw new NullPointerException("What happened?"); + * }</pre> + */ + public void expectMessage(Matcher<String> matcher) { + expect(hasMessage(matcher)); + } + + /** + * Verify that your code throws an exception whose cause is matched by + * a Hamcrest matcher. + * <pre> @Test + * public void throwsExceptionWhoseCauseCompliesWithMatcher() { + * NullPointerException expectedCause = new NullPointerException(); + * thrown.expectCause(is(expectedCause)); + * throw new IllegalArgumentException("What happened?", cause); + * }</pre> + */ + public void expectCause(Matcher<? extends Throwable> expectedCause) { + expect(hasCause(expectedCause)); + } + + private class ExpectedExceptionStatement extends Statement { + private final Statement next; + + public ExpectedExceptionStatement(Statement base) { + next = base; + } + + @Override + public void evaluate() throws Throwable { + try { + next.evaluate(); + } catch (Throwable e) { + handleException(e); + return; + } + if (isAnyExceptionExpected()) { + failDueToMissingException(); + } + } + } + + private void handleException(Throwable e) throws Throwable { + if (isAnyExceptionExpected()) { + assertThat(e, matcherBuilder.build()); + } else { + throw e; + } + } + + private boolean isAnyExceptionExpected() { + return matcherBuilder.expectsThrowable(); + } + + private void failDueToMissingException() throws AssertionError { + fail(missingExceptionMessage()); + } + + private String missingExceptionMessage() { + String expectation= StringDescription.toString(matcherBuilder.build()); + return format(missingExceptionMessage, expectation); + } } diff --git a/src/main/java/org/junit/rules/ExpectedExceptionMatcherBuilder.java b/src/main/java/org/junit/rules/ExpectedExceptionMatcherBuilder.java new file mode 100644 index 0000000..e7d94c4 --- /dev/null +++ b/src/main/java/org/junit/rules/ExpectedExceptionMatcherBuilder.java @@ -0,0 +1,46 @@ +package org.junit.rules; + +import static org.hamcrest.CoreMatchers.allOf; +import static org.junit.matchers.JUnitMatchers.isThrowable; + +import java.util.ArrayList; +import java.util.List; + +import org.hamcrest.Matcher; + +/** + * Builds special matcher used by {@link ExpectedException}. + */ +class ExpectedExceptionMatcherBuilder { + + private final List<Matcher<?>> matchers = new ArrayList<Matcher<?>>(); + + void add(Matcher<?> matcher) { + matchers.add(matcher); + } + + boolean expectsThrowable() { + return !matchers.isEmpty(); + } + + Matcher<Throwable> build() { + return isThrowable(allOfTheMatchers()); + } + + private Matcher<Throwable> allOfTheMatchers() { + if (matchers.size() == 1) { + return cast(matchers.get(0)); + } + return allOf(castedMatchers()); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private List<Matcher<? super Throwable>> castedMatchers() { + return new ArrayList<Matcher<? super Throwable>>((List) matchers); + } + + @SuppressWarnings("unchecked") + private Matcher<Throwable> cast(Matcher<?> singleMatcher) { + return (Matcher<Throwable>) singleMatcher; + } +} |