Skip to content

Commit 4bf17cf

Browse files
committed
hamcrest#329 Added declarative equivalent of JUnit's assertThrows
1 parent fa372d6 commit 4bf17cf

File tree

5 files changed

+357
-2
lines changed

5 files changed

+357
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.hamcrest;
2+
3+
public interface Executable {
4+
5+
void execute() throws Throwable;
6+
}

hamcrest/src/main/java/org/hamcrest/MatcherAssert.java

+28-1
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,37 @@ public static <T> void assertThat(String reason, T actual, Matcher<? super T> ma
2020
throw new AssertionError(description.toString());
2121
}
2222
}
23-
23+
2424
public static void assertThat(String reason, boolean assertion) {
2525
if (!assertion) {
2626
throw new AssertionError(reason);
2727
}
2828
}
29+
30+
public static <T extends Throwable> void assertThat(Executable executable, Throws<T> doesThrow) {
31+
assertThat("", executable, doesThrow);
32+
}
33+
34+
public static <T extends Throwable> void assertThat(String reason, Executable executable, Throws<T> doesThrow) {
35+
boolean executionDidNotThrow = false;
36+
try {
37+
executable.execute();
38+
executionDidNotThrow = true;
39+
} catch (Throwable actual) {
40+
assertThat(reason, (T) actual, doesThrow.asMatcher());
41+
} finally {
42+
if (executionDidNotThrow) {
43+
Description description = new StringDescription();
44+
description.appendText(reason)
45+
.appendText(System.lineSeparator())
46+
.appendText("Expected: ")
47+
.appendDescriptionOf(doesThrow)
48+
.appendText(System.lineSeparator())
49+
.appendText(" but: ");
50+
doesThrow.describeMismatch(description);
51+
52+
throw new AssertionError(description.toString());
53+
}
54+
}
55+
}
2956
}

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

+66
Original file line numberDiff line numberDiff line change
@@ -1712,5 +1712,71 @@ public static org.hamcrest.Matcher<org.w3c.dom.Node> hasXPath(java.lang.String x
17121712
return org.hamcrest.xml.HasXPath.hasXPath(xPath, namespaceContext);
17131713
}
17141714

1715+
/**
1716+
* Creates a {@link Throws} object that matches a throwable according to the given throwable matcher.
1717+
* This can be used with the {@link MatcherAssert#assertThat(String, Executable, Throws)} family of methods.
1718+
* For example:
1719+
* <pre>assertThat(() -&gt; methodCallThatThrowsException(), doesThrow(withMessage("file not found")))</pre>
1720+
*
1721+
* @param throwableMatcher
1722+
* the matcher for the throwable to match, which must not be {@code null}
1723+
*/
1724+
public static <T extends Throwable> Throws<T> doesThrow(Matcher<? super Throwable> throwableMatcher) {
1725+
return Throws.doesThrow(throwableMatcher);
1726+
}
1727+
1728+
/**
1729+
* Creates a {@link Throws} object that matches a throwable of the given type.
1730+
* This can be used with the {@link MatcherAssert#assertThat(String, Executable, Throws)} family of methods.
1731+
* For example:
1732+
* <pre>assertThat(() -&gt; methodCallThatThrowsIOException(), throwsInstanceOf(IOException.class))</pre>
1733+
* This is shorthand for {@code doesThrow(instanceOf(MyThrowable.class))}, to be used as equivalent for JUnit 5's
1734+
* {@code assertThrows(MyThrowable.class, () -> {})}.
1735+
*
1736+
* @param throwableType
1737+
* the type of the throwable to match, which must not be {@code null}
1738+
*/
1739+
public static <T extends Throwable> Throws<T> throwsInstanceOf(Class<T> throwableType) {
1740+
return Throws.throwsInstanceOf(throwableType);
1741+
}
1742+
1743+
/**
1744+
* Creates a matcher that matches a throwable by matching its message.
1745+
* This can be used with the {@link MatcherAssert#assertThat(String, Executable, Throws)} family of methods.
1746+
* For example:
1747+
* <pre>assertThat(() -&gt; methodCallThatThrowsException(), doesThrow(withMessage(startsWith("file not found"))))</pre>
1748+
*
1749+
* @param messageMatcher
1750+
* the matcher to match the throwable's message with, which must not be {@code null}
1751+
*/
1752+
public static <T extends Throwable> Matcher<T> withMessage(Matcher<String> messageMatcher) {
1753+
return Throws.withMessage(messageMatcher);
1754+
}
1755+
1756+
/**
1757+
* Creates a matcher that matches a throwable by its message.
1758+
* This can be used with the {@link MatcherAssert#assertThat(String, Executable, Throws)} family of methods.
1759+
* For example:
1760+
* <pre>assertThat(() -&gt; methodCallThatThrowsException(), doesThrow(withMessage("message")))</pre>
1761+
* This is shorthand for {@code doesThrow(withMessage(equalTo("message")))}.
1762+
*
1763+
* @param messageToMatch
1764+
* the message to match the throwable's message with, which must not be {@code null}
1765+
*/
1766+
public static <T extends Throwable> Matcher<T> withMessage(String messageToMatch) {
1767+
return withMessage(equalTo(messageToMatch));
1768+
}
17151769

1770+
/**
1771+
* Creates a matcher that matches an outer throwable by matching its inner cause.
1772+
* This can be used with the {@link MatcherAssert#assertThat(String, Executable, Throws)} family of methods.
1773+
* For example:
1774+
* <pre>assertThat(() -&gt; methodCallThatThrowsInvocationTargetException(), doesThrow(becauseOf(instanceOf(IOException.class))))</pre>
1775+
*
1776+
* @param causeMatcher
1777+
* the matcher to matcher the outer throwable's inner cause with, which must not be {@code null}
1778+
*/
1779+
public static <T extends Throwable> Matcher<T> becauseOf(Matcher<? extends Throwable> causeMatcher) {
1780+
return Throws.becauseOf(causeMatcher);
1781+
}
17161782
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package org.hamcrest;
2+
3+
import static java.util.Objects.requireNonNull;
4+
import static org.hamcrest.core.IsInstanceOf.instanceOf;
5+
6+
/**
7+
* @author Peter De Maeyer
8+
*/
9+
public final class Throws<T extends Throwable> implements SelfDescribing {
10+
11+
private final Matcher<? super T> matcher;
12+
13+
public Throws(final Matcher<? super T> throwableMatcher) {
14+
requireNonNull(throwableMatcher);
15+
this.matcher = new BaseMatcher<T>() {
16+
17+
@Override
18+
public boolean matches(Object actual) {
19+
return throwableMatcher.matches(actual);
20+
}
21+
22+
@Override
23+
public void describeTo(Description description) {
24+
description.appendText("throws ");
25+
throwableMatcher.describeTo(description);
26+
}
27+
28+
@Override
29+
public void describeMismatch(Object item, Description mismatchDescription) {
30+
mismatchDescription.appendText("threw but ");
31+
throwableMatcher.describeMismatch(item, mismatchDescription);
32+
}
33+
};
34+
}
35+
36+
Matcher<? super T> asMatcher() {
37+
return matcher;
38+
}
39+
40+
@Override
41+
public void describeTo(Description description) {
42+
matcher.describeTo(description);
43+
}
44+
45+
public void describeMismatch(Description description) {
46+
description.appendText("did not throw");
47+
}
48+
49+
public static <T extends Throwable> Matcher<T> withMessage(final Matcher<String> messageMatcher) {
50+
return new TypeSafeMatcher<T>() {
51+
52+
@Override
53+
protected boolean matchesSafely(T throwable) {
54+
return messageMatcher.matches(throwable.getMessage());
55+
}
56+
57+
@Override
58+
public void describeTo(Description description) {
59+
description.appendText("with message ");
60+
messageMatcher.describeTo(description);
61+
}
62+
63+
@Override
64+
protected void describeMismatchSafely(T item, Description mismatchDescription) {
65+
mismatchDescription.appendText("message ");
66+
messageMatcher.describeMismatch(item.getMessage(), mismatchDescription);
67+
}
68+
};
69+
}
70+
71+
public static <T extends Throwable> Matcher<T> becauseOf(final Matcher<? extends Throwable> causeMatcher) {
72+
return new TypeSafeMatcher<T>() {
73+
74+
@Override
75+
protected boolean matchesSafely(T throwable) {
76+
return causeMatcher.matches(throwable.getCause());
77+
}
78+
79+
@Override
80+
public void describeTo(Description description) {
81+
description.appendText("because of ");
82+
causeMatcher.describeTo(description);
83+
}
84+
85+
@Override
86+
protected void describeMismatchSafely(T item, Description mismatchDescription) {
87+
mismatchDescription.appendText("cause ");
88+
causeMatcher.describeMismatch(item.getCause(), mismatchDescription);
89+
}
90+
};
91+
}
92+
93+
public static <T extends Throwable> Throws<T> doesThrow(Matcher<? super T> throwableMatcher) {
94+
return new Throws<T>(throwableMatcher);
95+
}
96+
97+
public static <T extends Throwable> Throws<T> throwsInstanceOf(Class<T> throwableType) {
98+
return doesThrow(instanceOf(throwableType));
99+
}
100+
}

hamcrest/src/test/java/org/hamcrest/MatcherAssertTest.java

+157-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package org.hamcrest;
22

3+
import java.io.IOException;
4+
import javax.xml.stream.XMLStreamException;
5+
36
import org.junit.Test;
47

8+
import static org.hamcrest.Matchers.*;
59
import static org.hamcrest.MatcherAssert.assertThat;
6-
import static org.hamcrest.core.IsEqual.equalTo;
710
import static org.junit.Assert.*;
811

912
public final class MatcherAssertTest {
@@ -96,4 +99,157 @@ public void describeMismatch(Object item, Description mismatchDescription) {
9699
canAssertSubtypes() {
97100
assertThat(1, equalTo((Number) 1));
98101
}
102+
103+
@Test public void
104+
throwableIsOfMatchingInstance() {
105+
assertThat(
106+
new Executable() {
107+
@Override
108+
public void execute() {
109+
throw new IllegalStateException();
110+
}
111+
},
112+
throwsInstanceOf(IllegalStateException.class)
113+
);
114+
}
115+
116+
@Test public void
117+
throwableIsNotOfMatchingInstance() {
118+
String endLine = System.lineSeparator();
119+
String expectedMessage = endLine + "Expected: throws an instance of java.io.IOException" + endLine
120+
+ " but: threw but <java.lang.IllegalStateException> is a java.lang.IllegalStateException";
121+
try {
122+
assertThat(
123+
new Executable() {
124+
@Override
125+
public void execute() {
126+
throw new IllegalStateException();
127+
}
128+
},
129+
throwsInstanceOf(IOException.class)
130+
);
131+
fail("should have failed");
132+
}
133+
catch (AssertionError e) {
134+
assertEquals(expectedMessage, e.getMessage());
135+
}
136+
}
137+
138+
@Test public void
139+
throwableHasMatchingMessage() {
140+
assertThat(
141+
new Executable() {
142+
@Override
143+
public void execute() throws Exception {
144+
throw new Exception("message");
145+
}
146+
},
147+
doesThrow(withMessage(equalTo("message")))
148+
);
149+
}
150+
151+
@Test public void
152+
throwableDoesNotHaveMatchingMessage() {
153+
String endLine = System.lineSeparator();
154+
String expectedMessage = endLine + "Expected: throws with message \"expected message\"" + endLine
155+
+ " but: threw but message was \"actual message\"";
156+
try {
157+
assertThat(
158+
new Executable() {
159+
@Override
160+
public void execute() throws Exception {
161+
throw new Exception("actual message");
162+
}
163+
},
164+
doesThrow(withMessage("expected message"))
165+
);
166+
fail("should have failed");
167+
}
168+
catch (AssertionError e) {
169+
assertEquals(expectedMessage, e.getMessage());
170+
}
171+
}
172+
173+
@Test public void
174+
throwableExecutionDoesNotThrow() {
175+
String endLine = System.lineSeparator();
176+
String expectedMessage = endLine + "Expected: throws an instance of java.lang.NoSuchMethodError"
177+
+ endLine + " but: did not throw";
178+
try {
179+
assertThat(
180+
new Executable() {
181+
@Override
182+
public void execute() {
183+
// Do nothing
184+
}
185+
},
186+
throwsInstanceOf(NoSuchMethodError.class)
187+
);
188+
fail("should have failed");
189+
}
190+
catch (AssertionError e) {
191+
assertEquals(expectedMessage, e.getMessage());
192+
}
193+
}
194+
195+
@Test public void
196+
throwableCauseMatches() {
197+
Matcher<? extends Throwable> instanceOfMatcher = instanceOf(XMLStreamException.class);
198+
assertThat(
199+
new Executable() {
200+
@Override
201+
public void execute() {
202+
throw new RuntimeException(new XMLStreamException());
203+
}
204+
},
205+
doesThrow(becauseOf(instanceOfMatcher))
206+
);
207+
}
208+
209+
@Test public void
210+
throwableCauseDoesNotMatch() {
211+
String endLine = System.lineSeparator();
212+
String expectedMessage = endLine + "Expected: throws because of an instance of java.lang.NullPointerException"
213+
+ endLine + " but: threw but cause <java.lang.IllegalArgumentException> is a java.lang.IllegalArgumentException";
214+
try {
215+
Matcher<? extends Throwable> instanceOfMatcher = instanceOf(NullPointerException.class);
216+
assertThat(
217+
new Executable() {
218+
@Override
219+
public void execute() {
220+
throw new RuntimeException(new IllegalArgumentException());
221+
}
222+
},
223+
doesThrow(becauseOf(instanceOfMatcher))
224+
);
225+
fail("should have failed");
226+
}
227+
catch (AssertionError e) {
228+
assertEquals(expectedMessage, e.getMessage());
229+
}
230+
}
231+
232+
@Test public void
233+
throwableExecutionDoesNotMatchWithCustomMessage() {
234+
String endLine = System.lineSeparator();
235+
String expectedMessage = "Custom message"
236+
+ endLine + "Expected: throws an instance of java.lang.NullPointerException"
237+
+ endLine + " but: threw but <java.lang.IllegalArgumentException> is a java.lang.IllegalArgumentException";
238+
try {
239+
assertThat(
240+
"Custom message",
241+
new Executable() {
242+
@Override
243+
public void execute() {
244+
throw new IllegalArgumentException();
245+
}
246+
},
247+
throwsInstanceOf(NullPointerException.class)
248+
);
249+
fail("should have failed");
250+
}
251+
catch (AssertionError e) {
252+
assertEquals(expectedMessage, e.getMessage());
253+
}
254+
}
99255
}

0 commit comments

Comments
 (0)