diff --git a/hamcrest/src/main/java/org/hamcrest/Matchers.java b/hamcrest/src/main/java/org/hamcrest/Matchers.java index d5260fb1..300560ae 100644 --- a/hamcrest/src/main/java/org/hamcrest/Matchers.java +++ b/hamcrest/src/main/java/org/hamcrest/Matchers.java @@ -3,6 +3,7 @@ import org.hamcrest.collection.ArrayMatching; import org.hamcrest.core.IsIterableContaining; import org.hamcrest.core.StringRegularExpression; +import org.hamcrest.exception.ThrowsException; import org.hamcrest.optional.OptionalEmpty; import org.hamcrest.optional.OptionalWithValue; import org.hamcrest.text.IsEqualCompressingWhiteSpace; @@ -1972,21 +1973,21 @@ public static Matcher hasLength(org.hamcrest.Matcherargument. - * For example: - * - *
-     * assertThat("text", length(4))
-     * 
- * - * @param length the expected length of the string - * @return The matcher. - */ - public static Matcher hasLength(int length) { - return org.hamcrest.text.CharSequenceLength.hasLength(length); - } + /** + * Creates a matcher of {@link CharSequence} that matches when a char sequence has the length + * of the specified argument. + * For example: + * + *
+   * assertThat("text", length(4))
+   * 
+ * + * @param length the expected length of the string + * @return The matcher. + */ + public static Matcher hasLength(int length) { + return org.hamcrest.text.CharSequenceLength.hasLength(length); + } /** * Creates a matcher that matches any examined object whose toString method @@ -2228,4 +2229,76 @@ public static Matcher> optionalWithValue(T value) { public static Matcher> optionalWithValue(Matcher matcher) { return OptionalWithValue.optionalWithValue(matcher); } + + /** + * Matcher for {@link Throwable} that expects that the Runnable throws an exception + * + * @param type of the Runnable + * @return The matcher. + */ + public static ThrowsException throwsException() { + return ThrowsException.throwsException(); + } + + /** + * Matcher for {@link Throwable} that expects that the Runnable throws an exception of the provided throwableClass class + * + * @param type of the Runnable + * @param type of the Throwable + * @param throwableClass the Throwable class against which examined exceptions are compared + * @return The matcher. + */ + public static ThrowsException throwsException(Class throwableClass) { + return ThrowsException.throwsException(throwableClass); + } + + /** + * Matcher for {@link Throwable} that expects that the Runnable throws an exception of the provided throwableClass class and has a message equal to the provided message + * + * @param type of the Runnable + * @param type of the Throwable + * @param throwableClass the Throwable class against which examined exceptions are compared + * @param message the String against which examined exception messages are compared + * @return The matcher. + */ + public static ThrowsException throwsException(Class throwableClass, String message) { + return ThrowsException.throwsException(throwableClass, message); + } + + /** + * Matcher for {@link Throwable} that expects that the Runnable throws an exception of the provided throwableClass class and has a message matching the provided messageMatcher + * + * @param type of the Runnable + * @param type of the Throwable + * @param throwableClass the Throwable class against which examined exceptions are compared + * @param messageMatcher matcher to validate exception's message + * @return The matcher. + */ + public static ThrowsException throwsException(Class throwableClass, Matcher messageMatcher) { + return ThrowsException.throwsException(throwableClass, messageMatcher); + } + + /** + * Matcher for {@link Throwable} that expects that the Runnable throws an exception with a message equal to the provided message + * + * @param type of the Runnable + * @param type of the Throwable + * @param message the String against which examined exception messages are compared + * @return The matcher. + */ + public static ThrowsException throwsExceptionWithMessage(String message) { + return ThrowsException.throwsExceptionWithMessage(message); + } + + /** + * Matcher for {@link Throwable} that expects that the Runnable throws an exception with a message matching the provided messageMatcher + * + * @param type of the Runnable + * @param type of the Throwable + * @param messageMatcher matcher to validate exception's message + * @return The matcher. + */ + public static ThrowsException throwsExceptionWithMessage(Matcher messageMatcher) { + return ThrowsException.throwsExceptionWithMessage(messageMatcher); + } } diff --git a/hamcrest/src/main/java/org/hamcrest/exception/ThrowsException.java b/hamcrest/src/main/java/org/hamcrest/exception/ThrowsException.java new file mode 100644 index 00000000..19db5f90 --- /dev/null +++ b/hamcrest/src/main/java/org/hamcrest/exception/ThrowsException.java @@ -0,0 +1,80 @@ +package org.hamcrest.exception; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeDiagnosingMatcher; +import org.hamcrest.core.IsInstanceOf; + +import static org.hamcrest.core.IsAnything.anything; +import static org.hamcrest.core.IsEqual.equalTo; + +/** + * Tests if a Runnable throws a matching exception. + * + * @param the type of the matched Runnable + */ +public class ThrowsException extends TypeSafeDiagnosingMatcher { + private final IsInstanceOf classMatcher; + private final Matcher messageMatcher; + + public ThrowsException(IsInstanceOf classMatcher, Matcher messageMatcher) { + this.classMatcher = classMatcher; + this.messageMatcher = messageMatcher; + } + + public static ThrowsException throwsException() { + return throwsException(Throwable.class); + } + + public static ThrowsException throwsException(Class throwableClass) { + return new ThrowsException<>(new IsInstanceOf(throwableClass), anything("")); + } + + public static ThrowsException throwsException(Class throwableClass, String exactMessage) { + return throwsException(throwableClass, equalTo(exactMessage)); + } + + public static ThrowsException throwsException(Class throwableClass, Matcher messageMatcher) { + return new ThrowsException<>(new IsInstanceOf(throwableClass), messageMatcher); + } + + public static ThrowsException throwsExceptionWithMessage(String exactMessage) { + return throwsException(Throwable.class, equalTo(exactMessage)); + } + + public static ThrowsException throwsExceptionWithMessage(Matcher messageMatcher) { + return throwsException(Throwable.class, messageMatcher); + } + + @Override + protected boolean matchesSafely(T runnable, Description mismatchDescription) { + try { + runnable.run(); + mismatchDescription.appendText("the runnable didn't throw"); + return false; + } catch (Throwable t) { + boolean classMatches = classMatcher.matches(t); + if (!classMatches) { + mismatchDescription.appendText("thrown exception class was ").appendText(t.getClass().getName()); + } + + boolean messageMatches = messageMatcher.matches(t.getMessage()); + if (!messageMatches) { + if (!classMatches) { + mismatchDescription.appendText(" and the "); + } + mismatchDescription.appendText("thrown exception message "); + messageMatcher.describeMismatch(t.getMessage(), mismatchDescription); + } + + return classMatches && messageMatches; + } + } + + @Override + public void describeTo(Description description) { + description + .appendText("a runnable throwing ").appendDescriptionOf(classMatcher) + .appendText(" with message ").appendDescriptionOf(messageMatcher); + } +} diff --git a/hamcrest/src/test/java/org/hamcrest/exception/ThrowsExceptionTest.java b/hamcrest/src/test/java/org/hamcrest/exception/ThrowsExceptionTest.java new file mode 100644 index 00000000..37445065 --- /dev/null +++ b/hamcrest/src/test/java/org/hamcrest/exception/ThrowsExceptionTest.java @@ -0,0 +1,86 @@ +package org.hamcrest.exception; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.exception.ThrowsException.throwsException; +import static org.hamcrest.test.MatcherAssertions.*; + +public final class ThrowsExceptionTest { + + public static void throwIllegalArgumentException() { + throw new IllegalArgumentException("Boom!"); + } + + public static void throwNullPointerException() { + throw new NullPointerException("Boom!"); + } + + @Test + public void examples() { + assertThat(ThrowsExceptionTest::throwIllegalArgumentException, throwsException()); + assertThat(ThrowsExceptionTest::throwIllegalArgumentException, throwsException(RuntimeException.class)); + assertThat(ThrowsExceptionTest::throwIllegalArgumentException, throwsException(RuntimeException.class, "Boom!")); + assertThat(ThrowsExceptionTest::throwIllegalArgumentException, throwsException(RuntimeException.class, containsString("Boo"))); + } + + @Test + public void evaluatesToTrueIfRunnableThrowsExpectedExceptionWithMatchingMessage() { + assertMatches( + throwsException(IllegalArgumentException.class, "Boom!"), + ThrowsExceptionTest::throwIllegalArgumentException + ); + + assertDescription( + "a runnable throwing an instance of java.lang.IllegalArgumentException with message \"Boom!\"", + throwsException(IllegalArgumentException.class, "Boom!") + ); + + assertMismatchDescription( + "thrown exception message was \"Boom!\"", + throwsException(IllegalArgumentException.class, "Bang!"), + (Runnable) ThrowsExceptionTest::throwIllegalArgumentException + ); + + assertMismatchDescription( + "thrown exception class was java.lang.NullPointerException", + throwsException(IllegalArgumentException.class, "Boom!"), + (Runnable) ThrowsExceptionTest::throwNullPointerException + ); + + assertMismatchDescription( + "the runnable didn't throw", + throwsException(IllegalArgumentException.class, "Boom!"), + (Runnable) () -> { + } + ); + } + + @Test + public void evaluatesToTrueIfRunnableThrowsExceptionExtendingTheExpectedExceptionWithMatchingMessage() { + assertMatches( + throwsException(IllegalArgumentException.class, "Boom!"), + ThrowsExceptionTest::throwIllegalArgumentException + ); + } + + @Test + public void evaluatesToTrueIfRunnableThrowsExceptionWithMatchingMessage() { + assertMatches( + throwsException(IllegalArgumentException.class, containsString("Boo")), + ThrowsExceptionTest::throwIllegalArgumentException + ); + + assertDescription( + "a runnable throwing an instance of java.lang.IllegalArgumentException with message a string containing \"Boo\"", + throwsException(IllegalArgumentException.class, containsString("Boo")) + ); + + assertMismatchDescription( + "thrown exception class was java.lang.NullPointerException", + throwsException(IllegalArgumentException.class, containsString("Boo")), + (Runnable) ThrowsExceptionTest::throwNullPointerException + ); + } +}