Skip to content

Commit f9101be

Browse files
committed
Change: fix composition chain.
`ThrowsException` is now responsible for safely executing the runnable, extracting the exception (if any), and delegating the actual test to the composed matcher.
1 parent 2977d15 commit f9101be

File tree

5 files changed

+164
-105
lines changed

5 files changed

+164
-105
lines changed

hamcrest/src/main/java/org/hamcrest/exception/ThrowsException.java

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,44 +4,58 @@
44
import org.hamcrest.Matcher;
55
import org.hamcrest.TypeSafeMatcher;
66

7-
import static org.hamcrest.exception.ThrowsExceptionEqual.throwsExceptionEqual;
7+
import static org.hamcrest.exception.ThrowsExceptionEqualTo.exceptionEqualTo;
88

99
/**
1010
* Tests if a Runnable throws a matching exception.
11+
*
12+
* @param <T> the type of the matched Runnable
1113
*/
12-
public class ThrowsException extends TypeSafeMatcher<Runnable> {
14+
public class ThrowsException<T extends Runnable> extends TypeSafeMatcher<T> {
1315

14-
private final Matcher<Runnable> elementMatcher;
16+
private final Matcher<? super Throwable> exceptionMatcher;
17+
private Throwable actualException;
1518

1619
/**
1720
* Constructor, best called from one of the static factory methods.
21+
*
1822
* @param elementMatcher matches the expected element
1923
*/
20-
ThrowsException(Matcher<Runnable> elementMatcher) {
21-
super(Runnable.class);
22-
this.elementMatcher = elementMatcher;
24+
ThrowsException(Matcher<? super Throwable> elementMatcher) {
25+
this.exceptionMatcher = elementMatcher;
2326
}
2427

25-
public static <U extends Throwable> ThrowsException throwsException(U item) {
26-
return new ThrowsException(throwsExceptionEqual(item));
28+
public static <U extends Runnable, T extends Throwable> ThrowsException<U> throwsException(T item) {
29+
return new ThrowsException<>(exceptionEqualTo(item));
2730
}
2831

29-
public static ThrowsException throwsException(Matcher<Runnable> itemMatcher) {
30-
return new ThrowsException(itemMatcher);
32+
public static <U extends Runnable> ThrowsException<U> throwsException(Matcher<? super Throwable> exceptionMatcher) {
33+
return new ThrowsException<>(exceptionMatcher);
3134
}
3235

3336
@Override
34-
protected boolean matchesSafely(Runnable item) {
35-
return elementMatcher.matches(item);
37+
public boolean matchesSafely(T runnable) {
38+
try {
39+
runnable.run();
40+
return false;
41+
} catch (Throwable t) {
42+
actualException = t;
43+
return exceptionMatcher.matches(t);
44+
}
3645
}
3746

3847
@Override
3948
public void describeTo(Description description) {
40-
description.appendText("a runnable throwing ").appendDescriptionOf(elementMatcher);
49+
description.appendText("a runnable throwing ").appendDescriptionOf(exceptionMatcher);
4150
}
4251

4352
@Override
44-
protected void describeMismatchSafely(Runnable item, Description mismatchDescription) {
45-
elementMatcher.describeMismatch(item, mismatchDescription);
53+
public void describeMismatchSafely(T runnable, Description mismatchDescription) {
54+
if (actualException == null) {
55+
mismatchDescription.appendText("the runnable didn't throw");
56+
return;
57+
}
58+
mismatchDescription.appendText("the runnable threw ");
59+
exceptionMatcher.describeMismatch(actualException, mismatchDescription);
4660
}
4761
}

hamcrest/src/main/java/org/hamcrest/exception/ThrowsExceptionEqual.java

Lines changed: 0 additions & 53 deletions
This file was deleted.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package org.hamcrest.exception;
2+
3+
import org.hamcrest.Description;
4+
import org.hamcrest.TypeSafeMatcher;
5+
6+
/**
7+
* A matcher that checks if a Runnable throws an expected exception when run.
8+
*
9+
* @param <T> the type of the expected exception.
10+
*/
11+
public class ThrowsExceptionEqualTo<T extends Throwable> extends TypeSafeMatcher<T> {
12+
private final T expectedException;
13+
14+
/**
15+
* Constructor, best called from one of the static factory methods.
16+
*
17+
* @param expectedException the expected exception.
18+
*/
19+
ThrowsExceptionEqualTo(T expectedException) {
20+
this.expectedException = expectedException;
21+
}
22+
23+
public static <U extends Throwable> ThrowsExceptionEqualTo<U> exceptionEqualTo(U expectedException) {
24+
return new ThrowsExceptionEqualTo<>(expectedException);
25+
}
26+
27+
@Override
28+
public boolean matchesSafely(T item) {
29+
return this.expectedException.getClass().isAssignableFrom(item.getClass()) && item.getMessage().equals(this.expectedException.getMessage());
30+
}
31+
32+
@Override
33+
public void describeTo(Description description) {
34+
description.appendValue(expectedException.getClass().getName()).appendText(" with message ").appendValue(expectedException.getMessage());
35+
}
36+
37+
@Override
38+
protected void describeMismatchSafely(T item, Description mismatchDescription) {
39+
if (this.expectedException.getClass().isAssignableFrom(item.getClass())) {
40+
mismatchDescription.appendValue(item.getClass().getName()).appendText(" with message ").appendValue(item.getMessage()).appendText(" instead of ").appendValue(this.expectedException.getMessage());
41+
return;
42+
}
43+
mismatchDescription.appendValue(item.getClass().getName()).appendText(" instead of a ").appendValue(this.expectedException.getClass().getName()).appendText(" exception");
44+
}
45+
}

hamcrest/src/main/java/org/hamcrest/exception/ThrowsExceptionWithMessage.java

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,46 +11,38 @@
1111
*
1212
* @param <T> the type of the expected exception.
1313
*/
14-
public class ThrowsExceptionWithMessage<T extends String> extends TypeSafeMatcher<Runnable> {
15-
private final Matcher<? super T> messageMatcher;
16-
private String actualMessage;
14+
public class ThrowsExceptionWithMessage<T extends Throwable> extends TypeSafeMatcher<T> {
15+
private final Matcher<? super String> messageMatcher;
1716

1817
/**
1918
* Constructor, best called from one of the static factory methods.
2019
*
2120
* @param messageMatcher matches the exception message
2221
*/
23-
ThrowsExceptionWithMessage(Matcher<? super T> messageMatcher) {
24-
super(Runnable.class);
22+
ThrowsExceptionWithMessage(Matcher<? super String> messageMatcher) {
2523
this.messageMatcher = messageMatcher;
2624
}
2725

28-
public static <U extends String> ThrowsExceptionWithMessage<U> withMessage(U message) {
26+
public static <U extends Throwable, V extends String> ThrowsExceptionWithMessage<U> withMessage(V message) {
2927
return new ThrowsExceptionWithMessage<>(equalTo(message));
3028
}
3129

32-
public static <U extends String> ThrowsExceptionWithMessage<U> withMessage(Matcher<? super U> messageMatcher) {
30+
public static <U extends Throwable> ThrowsExceptionWithMessage<U> withMessage(Matcher<? super String> messageMatcher) {
3331
return new ThrowsExceptionWithMessage<>(messageMatcher);
3432
}
3533

3634
@Override
37-
protected boolean matchesSafely(Runnable item) {
38-
try {
39-
item.run();
40-
return false;
41-
} catch (Throwable t) {
42-
actualMessage = t.getMessage();
43-
return this.messageMatcher.matches(t.getMessage());
44-
}
35+
protected boolean matchesSafely(T item) {
36+
return this.messageMatcher.matches(item.getMessage());
4537
}
4638

4739
@Override
4840
public void describeTo(Description description) {
49-
description.appendText("a runnable throwing an exception with message ").appendDescriptionOf(messageMatcher);
41+
description.appendText("an exception with message ").appendDescriptionOf(messageMatcher);
5042
}
5143

5244
@Override
53-
protected void describeMismatchSafely(Runnable item, Description mismatchDescription) {
54-
mismatchDescription.appendText("exception message was ").appendValue(actualMessage).appendText(" instead of ").appendDescriptionOf(messageMatcher);
45+
protected void describeMismatchSafely(T item, Description mismatchDescription) {
46+
mismatchDescription.appendText("an exception with message ").appendValue(item.getMessage()).appendText(" instead of ").appendDescriptionOf(messageMatcher);
5547
}
5648
}

hamcrest/src/test/java/org/hamcrest/exception/ThrowsExceptionEqualTest.java

Lines changed: 80 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,12 @@
44
import org.hamcrest.Matcher;
55

66
import static org.hamcrest.Matchers.containsString;
7+
import static org.hamcrest.Matchers.instanceOf;
78
import static org.hamcrest.exception.ThrowsException.throwsException;
89
import static org.hamcrest.exception.ThrowsExceptionWithMessage.withMessage;
910

1011
public class ThrowsExceptionEqualTest extends AbstractMatcherTest {
1112

12-
private Runnable runnableThrowing(String message) {
13-
return new ThrowingRunnable(new IllegalArgumentException(message));
14-
}
15-
1613
private Runnable runnableThrowing(Throwable exception) {
1714
return new ThrowingRunnable(exception);
1815
}
@@ -23,31 +20,95 @@ protected Matcher<?> createMatcher() {
2320
}
2421

2522
public void testEvaluatesToTrueIfRunnableThrowsExpectedExceptionWithMatchingMessage() {
26-
Matcher<Runnable> matcher = throwsException(new IllegalArgumentException("Boom!"));
27-
28-
assertMatches(matcher, runnableThrowing("Boom!"));
29-
assertDoesNotMatch(matcher, runnableThrowing("Bang!"));
30-
assertMismatchDescription("threw \"java.lang.IllegalArgumentException\" with message \"Bang!\" instead of \"Boom!\"", matcher, runnableThrowing("Bang!"));
31-
assertDoesNotMatch(matcher, runnableThrowing(new NullPointerException("Boom!")));
32-
assertMismatchDescription("threw a \"java.lang.NullPointerException\" instead of a \"java.lang.IllegalArgumentException\" exception", matcher, runnableThrowing(new NullPointerException("Boom!")));
23+
assertMatches(
24+
throwsException(new IllegalArgumentException("Boom!")),
25+
runnableThrowing(new IllegalArgumentException("Boom!"))
26+
);
27+
28+
assertDescription(
29+
"a runnable throwing \"java.lang.IllegalArgumentException\" with message \"Boom!\"",
30+
throwsException(new IllegalArgumentException("Boom!"))
31+
);
32+
33+
assertMismatchDescription(
34+
"the runnable threw \"java.lang.IllegalArgumentException\" with message \"Bang!\" instead of \"Boom!\"",
35+
throwsException(new IllegalArgumentException("Boom!")),
36+
runnableThrowing(new IllegalArgumentException("Bang!"))
37+
);
38+
39+
assertMismatchDescription(
40+
"the runnable threw \"java.lang.NullPointerException\" instead of a \"java.lang.IllegalArgumentException\" exception",
41+
throwsException(new IllegalArgumentException("Boom!")),
42+
runnableThrowing(new NullPointerException("Boom!"))
43+
);
44+
45+
assertMismatchDescription(
46+
"the runnable didn't throw",
47+
throwsException(new IllegalArgumentException("Boom!")),
48+
(Runnable) () -> {}
49+
);
3350
}
3451

3552
public void testEvaluatesToTrueIfRunnableThrowsExceptionExtendingTheExpectedExceptionWithMatchingMessage() {
36-
Matcher<Runnable> matcher = throwsException(new IllegalArgumentException("Boom!"));
37-
38-
assertMatches(matcher, runnableThrowing(new TestException("Boom!")));
53+
assertMatches(
54+
throwsException(new IllegalArgumentException("Boom!")),
55+
runnableThrowing(new TestException("Boom!"))
56+
);
3957
}
4058

4159
public void testEvaluatesToTrueIfRunnableThrowsExceptionWithExpectedMessage() {
42-
Matcher<Runnable> matcher = throwsException(withMessage("Boom!"));
43-
44-
assertMatches(matcher, runnableThrowing(new TestException("Boom!")));
60+
assertMatches(
61+
throwsException(withMessage("Boom!")),
62+
runnableThrowing(new TestException("Boom!"))
63+
);
64+
65+
assertDescription(
66+
"a runnable throwing an exception with message \"Boom!\"",
67+
throwsException(withMessage("Boom!"))
68+
);
69+
70+
assertMismatchDescription(
71+
"the runnable threw an exception with message \"Bang!\" instead of \"Boom!\"",
72+
throwsException(withMessage("Boom!")),
73+
runnableThrowing(new IllegalArgumentException("Bang!"))
74+
);
4575
}
4676

4777
public void testEvaluatesToTrueIfRunnableThrowsExceptionWithMatchingMessage() {
48-
Matcher<Runnable> matcher = throwsException(withMessage(containsString("bar")));
4978

50-
assertMatches(matcher, runnableThrowing(new TestException("Foo bar baz")));
79+
assertMatches(
80+
throwsException(withMessage(containsString("bar"))),
81+
runnableThrowing(new TestException("Foo bar baz"))
82+
);
83+
84+
assertDescription(
85+
"a runnable throwing an exception with message a string containing \"bar\"",
86+
throwsException(withMessage(containsString("bar")))
87+
);
88+
89+
assertMismatchDescription(
90+
"the runnable threw an exception with message \"Bang!\" instead of a string containing \"bar\"",
91+
throwsException(withMessage(containsString("bar"))),
92+
runnableThrowing(new IllegalArgumentException("Bang!"))
93+
);
94+
}
95+
96+
public void testEvaluatesToTrueIfRunnableThrowsMatchingException() {
97+
assertMatches(
98+
throwsException(instanceOf(TestException.class)),
99+
runnableThrowing(new TestException("Boom!"))
100+
);
101+
102+
assertDescription(
103+
"a runnable throwing an instance of java.lang.NullPointerException",
104+
throwsException(instanceOf(NullPointerException.class))
105+
);
106+
107+
assertMismatchDescription(
108+
"the runnable threw <java.lang.IllegalArgumentException: Bang!> is a java.lang.IllegalArgumentException",
109+
throwsException(instanceOf(NullPointerException.class)),
110+
runnableThrowing(new IllegalArgumentException("Bang!"))
111+
);
51112
}
52113

53114
public static class TestException extends IllegalArgumentException {

0 commit comments

Comments
 (0)