Skip to content

Commit 1e4230f

Browse files
authored
Add matcher for thrown exceptions in Runnable (#423)
1 parent 1bed2f5 commit 1e4230f

File tree

3 files changed

+254
-15
lines changed

3 files changed

+254
-15
lines changed

hamcrest/src/main/java/org/hamcrest/Matchers.java

Lines changed: 88 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.hamcrest.collection.ArrayMatching;
44
import org.hamcrest.core.IsIterableContaining;
55
import org.hamcrest.core.StringRegularExpression;
6+
import org.hamcrest.exception.ThrowsException;
67
import org.hamcrest.optional.OptionalEmpty;
78
import org.hamcrest.optional.OptionalWithValue;
89
import org.hamcrest.text.IsEqualCompressingWhiteSpace;
@@ -1972,21 +1973,21 @@ public static Matcher<CharSequence> hasLength(org.hamcrest.Matcher<? super java.
19721973
return org.hamcrest.text.CharSequenceLength.hasLength(lengthMatcher);
19731974
}
19741975

1975-
/**
1976-
* Creates a matcher of {@link CharSequence} that matches when a char sequence has the length
1977-
* of the specified <code>argument</code>.
1978-
* For example:
1979-
*
1980-
* <pre>
1981-
* assertThat("text", length(4))
1982-
* </pre>
1983-
*
1984-
* @param length the expected length of the string
1985-
* @return The matcher.
1986-
*/
1987-
public static Matcher<CharSequence> hasLength(int length) {
1988-
return org.hamcrest.text.CharSequenceLength.hasLength(length);
1989-
}
1976+
/**
1977+
* Creates a matcher of {@link CharSequence} that matches when a char sequence has the length
1978+
* of the specified <code>argument</code>.
1979+
* For example:
1980+
*
1981+
* <pre>
1982+
* assertThat("text", length(4))
1983+
* </pre>
1984+
*
1985+
* @param length the expected length of the string
1986+
* @return The matcher.
1987+
*/
1988+
public static Matcher<CharSequence> hasLength(int length) {
1989+
return org.hamcrest.text.CharSequenceLength.hasLength(length);
1990+
}
19901991

19911992
/**
19921993
* Creates a matcher that matches any examined object whose <code>toString</code> method
@@ -2228,4 +2229,76 @@ public static <T> Matcher<Optional<T>> optionalWithValue(T value) {
22282229
public static <T> Matcher<Optional<T>> optionalWithValue(Matcher<? super T> matcher) {
22292230
return OptionalWithValue.optionalWithValue(matcher);
22302231
}
2232+
2233+
/**
2234+
* Matcher for {@link Throwable} that expects that the Runnable throws an exception
2235+
*
2236+
* @param <T> type of the Runnable
2237+
* @return The matcher.
2238+
*/
2239+
public static <T extends Runnable> ThrowsException<T> throwsException() {
2240+
return ThrowsException.throwsException();
2241+
}
2242+
2243+
/**
2244+
* Matcher for {@link Throwable} that expects that the Runnable throws an exception of the provided <code>throwableClass</code> class
2245+
*
2246+
* @param <U> type of the Runnable
2247+
* @param <T> type of the Throwable
2248+
* @param throwableClass the Throwable class against which examined exceptions are compared
2249+
* @return The matcher.
2250+
*/
2251+
public static <T extends Runnable, U extends Throwable> ThrowsException<T> throwsException(Class<U> throwableClass) {
2252+
return ThrowsException.throwsException(throwableClass);
2253+
}
2254+
2255+
/**
2256+
* Matcher for {@link Throwable} that expects that the Runnable throws an exception of the provided <code>throwableClass</code> class and has a message equal to the provided <code>message</code>
2257+
*
2258+
* @param <T> type of the Runnable
2259+
* @param <U> type of the Throwable
2260+
* @param throwableClass the Throwable class against which examined exceptions are compared
2261+
* @param message the String against which examined exception messages are compared
2262+
* @return The matcher.
2263+
*/
2264+
public static <T extends Runnable, U extends Throwable> ThrowsException<T> throwsException(Class<U> throwableClass, String message) {
2265+
return ThrowsException.throwsException(throwableClass, message);
2266+
}
2267+
2268+
/**
2269+
* Matcher for {@link Throwable} that expects that the Runnable throws an exception of the provided <code>throwableClass</code> class and has a message matching the provided <code>messageMatcher</code>
2270+
*
2271+
* @param <T> type of the Runnable
2272+
* @param <U> type of the Throwable
2273+
* @param throwableClass the Throwable class against which examined exceptions are compared
2274+
* @param messageMatcher matcher to validate exception's message
2275+
* @return The matcher.
2276+
*/
2277+
public static <T extends Runnable, U extends Throwable> ThrowsException<T> throwsException(Class<U> throwableClass, Matcher<String> messageMatcher) {
2278+
return ThrowsException.throwsException(throwableClass, messageMatcher);
2279+
}
2280+
2281+
/**
2282+
* Matcher for {@link Throwable} that expects that the Runnable throws an exception with a message equal to the provided <code>message</code>
2283+
*
2284+
* @param <T> type of the Runnable
2285+
* @param <U> type of the Throwable
2286+
* @param message the String against which examined exception messages are compared
2287+
* @return The matcher.
2288+
*/
2289+
public static <T extends Runnable, U extends Throwable> ThrowsException<T> throwsExceptionWithMessage(String message) {
2290+
return ThrowsException.throwsExceptionWithMessage(message);
2291+
}
2292+
2293+
/**
2294+
* Matcher for {@link Throwable} that expects that the Runnable throws an exception with a message matching the provided <code>messageMatcher</code>
2295+
*
2296+
* @param <T> type of the Runnable
2297+
* @param <U> type of the Throwable
2298+
* @param messageMatcher matcher to validate exception's message
2299+
* @return The matcher.
2300+
*/
2301+
public static <T extends Runnable, U extends Throwable> ThrowsException<T> throwsExceptionWithMessage(Matcher<String> messageMatcher) {
2302+
return ThrowsException.throwsExceptionWithMessage(messageMatcher);
2303+
}
22312304
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package org.hamcrest.exception;
2+
3+
import org.hamcrest.Description;
4+
import org.hamcrest.Matcher;
5+
import org.hamcrest.TypeSafeDiagnosingMatcher;
6+
import org.hamcrest.core.IsInstanceOf;
7+
8+
import static org.hamcrest.core.IsAnything.anything;
9+
import static org.hamcrest.core.IsEqual.equalTo;
10+
11+
/**
12+
* Tests if a Runnable throws a matching exception.
13+
*
14+
* @param <T> the type of the matched Runnable
15+
*/
16+
public class ThrowsException<T extends Runnable> extends TypeSafeDiagnosingMatcher<T> {
17+
private final IsInstanceOf classMatcher;
18+
private final Matcher<? super String> messageMatcher;
19+
20+
public ThrowsException(IsInstanceOf classMatcher, Matcher<? super String> messageMatcher) {
21+
this.classMatcher = classMatcher;
22+
this.messageMatcher = messageMatcher;
23+
}
24+
25+
public static <T extends Runnable> ThrowsException<T> throwsException() {
26+
return throwsException(Throwable.class);
27+
}
28+
29+
public static <T extends Runnable, U extends Throwable> ThrowsException<T> throwsException(Class<U> throwableClass) {
30+
return new ThrowsException<>(new IsInstanceOf(throwableClass), anything("<anything>"));
31+
}
32+
33+
public static <T extends Runnable, U extends Throwable> ThrowsException<T> throwsException(Class<U> throwableClass, String exactMessage) {
34+
return throwsException(throwableClass, equalTo(exactMessage));
35+
}
36+
37+
public static <T extends Runnable, U extends Throwable> ThrowsException<T> throwsException(Class<U> throwableClass, Matcher<String> messageMatcher) {
38+
return new ThrowsException<>(new IsInstanceOf(throwableClass), messageMatcher);
39+
}
40+
41+
public static <T extends Runnable> ThrowsException<T> throwsExceptionWithMessage(String exactMessage) {
42+
return throwsException(Throwable.class, equalTo(exactMessage));
43+
}
44+
45+
public static <T extends Runnable> ThrowsException<T> throwsExceptionWithMessage(Matcher<String> messageMatcher) {
46+
return throwsException(Throwable.class, messageMatcher);
47+
}
48+
49+
@Override
50+
protected boolean matchesSafely(T runnable, Description mismatchDescription) {
51+
try {
52+
runnable.run();
53+
mismatchDescription.appendText("the runnable didn't throw");
54+
return false;
55+
} catch (Throwable t) {
56+
boolean classMatches = classMatcher.matches(t);
57+
if (!classMatches) {
58+
mismatchDescription.appendText("thrown exception class was ").appendText(t.getClass().getName());
59+
}
60+
61+
boolean messageMatches = messageMatcher.matches(t.getMessage());
62+
if (!messageMatches) {
63+
if (!classMatches) {
64+
mismatchDescription.appendText(" and the ");
65+
}
66+
mismatchDescription.appendText("thrown exception message ");
67+
messageMatcher.describeMismatch(t.getMessage(), mismatchDescription);
68+
}
69+
70+
return classMatches && messageMatches;
71+
}
72+
}
73+
74+
@Override
75+
public void describeTo(Description description) {
76+
description
77+
.appendText("a runnable throwing ").appendDescriptionOf(classMatcher)
78+
.appendText(" with message ").appendDescriptionOf(messageMatcher);
79+
}
80+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package org.hamcrest.exception;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import static org.hamcrest.MatcherAssert.assertThat;
6+
import static org.hamcrest.Matchers.containsString;
7+
import static org.hamcrest.exception.ThrowsException.throwsException;
8+
import static org.hamcrest.test.MatcherAssertions.*;
9+
10+
public final class ThrowsExceptionTest {
11+
12+
public static void throwIllegalArgumentException() {
13+
throw new IllegalArgumentException("Boom!");
14+
}
15+
16+
public static void throwNullPointerException() {
17+
throw new NullPointerException("Boom!");
18+
}
19+
20+
@Test
21+
public void examples() {
22+
assertThat(ThrowsExceptionTest::throwIllegalArgumentException, throwsException());
23+
assertThat(ThrowsExceptionTest::throwIllegalArgumentException, throwsException(RuntimeException.class));
24+
assertThat(ThrowsExceptionTest::throwIllegalArgumentException, throwsException(RuntimeException.class, "Boom!"));
25+
assertThat(ThrowsExceptionTest::throwIllegalArgumentException, throwsException(RuntimeException.class, containsString("Boo")));
26+
}
27+
28+
@Test
29+
public void evaluatesToTrueIfRunnableThrowsExpectedExceptionWithMatchingMessage() {
30+
assertMatches(
31+
throwsException(IllegalArgumentException.class, "Boom!"),
32+
ThrowsExceptionTest::throwIllegalArgumentException
33+
);
34+
35+
assertDescription(
36+
"a runnable throwing an instance of java.lang.IllegalArgumentException with message \"Boom!\"",
37+
throwsException(IllegalArgumentException.class, "Boom!")
38+
);
39+
40+
assertMismatchDescription(
41+
"thrown exception message was \"Boom!\"",
42+
throwsException(IllegalArgumentException.class, "Bang!"),
43+
(Runnable) ThrowsExceptionTest::throwIllegalArgumentException
44+
);
45+
46+
assertMismatchDescription(
47+
"thrown exception class was java.lang.NullPointerException",
48+
throwsException(IllegalArgumentException.class, "Boom!"),
49+
(Runnable) ThrowsExceptionTest::throwNullPointerException
50+
);
51+
52+
assertMismatchDescription(
53+
"the runnable didn't throw",
54+
throwsException(IllegalArgumentException.class, "Boom!"),
55+
(Runnable) () -> {
56+
}
57+
);
58+
}
59+
60+
@Test
61+
public void evaluatesToTrueIfRunnableThrowsExceptionExtendingTheExpectedExceptionWithMatchingMessage() {
62+
assertMatches(
63+
throwsException(IllegalArgumentException.class, "Boom!"),
64+
ThrowsExceptionTest::throwIllegalArgumentException
65+
);
66+
}
67+
68+
@Test
69+
public void evaluatesToTrueIfRunnableThrowsExceptionWithMatchingMessage() {
70+
assertMatches(
71+
throwsException(IllegalArgumentException.class, containsString("Boo")),
72+
ThrowsExceptionTest::throwIllegalArgumentException
73+
);
74+
75+
assertDescription(
76+
"a runnable throwing an instance of java.lang.IllegalArgumentException with message a string containing \"Boo\"",
77+
throwsException(IllegalArgumentException.class, containsString("Boo"))
78+
);
79+
80+
assertMismatchDescription(
81+
"thrown exception class was java.lang.NullPointerException",
82+
throwsException(IllegalArgumentException.class, containsString("Boo")),
83+
(Runnable) ThrowsExceptionTest::throwNullPointerException
84+
);
85+
}
86+
}

0 commit comments

Comments
 (0)