From 7844e8d6a5bd8debfc472ddcafbc4c5c3029e623 Mon Sep 17 00:00:00 2001 From: Peter De Maeyer Date: Sun, 21 Feb 2021 09:10:55 +0100 Subject: [PATCH 1/3] #329 Added declarative equivalent of JUnit's assertThrows --- .../main/java/org/hamcrest/Executable.java | 7 ++ .../main/java/org/hamcrest/MatcherAssert.java | 29 ++++- .../src/main/java/org/hamcrest/Matchers.java | 66 ++++++++++ .../src/main/java/org/hamcrest/Throws.java | 100 +++++++++++++++ .../java/org/hamcrest/MatcherAssertTest.java | 116 +++++++++++++++++- 5 files changed, 316 insertions(+), 2 deletions(-) create mode 100644 hamcrest/src/main/java/org/hamcrest/Executable.java create mode 100644 hamcrest/src/main/java/org/hamcrest/Throws.java diff --git a/hamcrest/src/main/java/org/hamcrest/Executable.java b/hamcrest/src/main/java/org/hamcrest/Executable.java new file mode 100644 index 00000000..fb964219 --- /dev/null +++ b/hamcrest/src/main/java/org/hamcrest/Executable.java @@ -0,0 +1,7 @@ +package org.hamcrest; + +@FunctionalInterface +public interface Executable { + + void execute() throws Throwable; +} diff --git a/hamcrest/src/main/java/org/hamcrest/MatcherAssert.java b/hamcrest/src/main/java/org/hamcrest/MatcherAssert.java index bc001ebb..e52b9858 100644 --- a/hamcrest/src/main/java/org/hamcrest/MatcherAssert.java +++ b/hamcrest/src/main/java/org/hamcrest/MatcherAssert.java @@ -20,10 +20,37 @@ public static void assertThat(String reason, T actual, Matcher ma throw new AssertionError(description.toString()); } } - + public static void assertThat(String reason, boolean assertion) { if (!assertion) { throw new AssertionError(reason); } } + + public static void assertThat(Executable executable, Throws doesThrow) { + assertThat("", executable, doesThrow); + } + + public static void assertThat(String reason, Executable executable, Throws doesThrow) { + boolean executionDidNotThrow = false; + try { + executable.execute(); + executionDidNotThrow = true; + } catch (Throwable actual) { + assertThat(reason, (T) actual, doesThrow.asMatcher()); + } finally { + if (executionDidNotThrow) { + Description description = new StringDescription(); + description.appendText(reason) + .appendText(System.lineSeparator()) + .appendText("Expected: ") + .appendDescriptionOf(doesThrow) + .appendText(System.lineSeparator()) + .appendText(" but: "); + doesThrow.describeMismatch(description); + + throw new AssertionError(description.toString()); + } + } + } } diff --git a/hamcrest/src/main/java/org/hamcrest/Matchers.java b/hamcrest/src/main/java/org/hamcrest/Matchers.java index d4f543ea..32125b9b 100644 --- a/hamcrest/src/main/java/org/hamcrest/Matchers.java +++ b/hamcrest/src/main/java/org/hamcrest/Matchers.java @@ -1712,5 +1712,71 @@ public static org.hamcrest.Matcher hasXPath(java.lang.String x return org.hamcrest.xml.HasXPath.hasXPath(xPath, namespaceContext); } + /** + * Creates a {@link Throws} object that matches a throwable according to the given throwable matcher. + * This can be used with the {@link MatcherAssert#assertThat(String, Executable, Throws)} family of methods. + * For example: + *
assertThat(() -> methodCallThatThrowsException(), doesThrow(withMessage("file not found")))
+ * + * @param throwableMatcher + * the matcher for the throwable to match, which must not be {@code null} + */ + public static Throws doesThrow(Matcher throwableMatcher) { + return Throws.doesThrow(throwableMatcher); + } + + /** + * Creates a {@link Throws} object that matches a throwable of the given type. + * This can be used with the {@link MatcherAssert#assertThat(String, Executable, Throws)} family of methods. + * For example: + *
assertThat(() -> methodCallThatThrowsIOException(), throwsInstanceOf(IOException.class))
+ * This is shorthand for {@code doesThrow(instanceOf(MyThrowable.class))}, to be used as equivalent for JUnit 5's + * {@code assertThrows(MyThrowable.class, () -> {})}. + * + * @param throwableType + * the type of the throwable to match, which must not be {@code null} + */ + public static Throws throwsInstanceOf(Class throwableType) { + return Throws.throwsInstanceOf(throwableType); + } + + /** + * Creates a matcher that matches a throwable by matching its message. + * This can be used with the {@link MatcherAssert#assertThat(String, Executable, Throws)} family of methods. + * For example: + *
assertThat(() -> methodCallThatThrowsException(), doesThrow(withMessage(startsWith("file not found"))))
+ * + * @param messageMatcher + * the matcher to match the throwable's message with, which must not be {@code null} + */ + public static Matcher withMessage(Matcher messageMatcher) { + return Throws.withMessage(messageMatcher); + } + + /** + * Creates a matcher that matches a throwable by its message. + * This can be used with the {@link MatcherAssert#assertThat(String, Executable, Throws)} family of methods. + * For example: + *
assertThat(() -> methodCallThatThrowsException(), doesThrow(withMessage("message")))
+ * This is shorthand for {@code doesThrow(withMessage(equalTo("message")))}. + * + * @param messageToMatch + * the message to match the throwable's message with, which must not be {@code null} + */ + public static Matcher withMessage(String messageToMatch) { + return withMessage(equalTo(messageToMatch)); + } + /** + * Creates a matcher that matches an outer throwable by matching its inner cause. + * This can be used with the {@link MatcherAssert#assertThat(String, Executable, Throws)} family of methods. + * For example: + *
assertThat(() -> methodCallThatThrowsInvocationTargetException(), doesThrow(becauseOf(instanceOf(IOException.class))))
+ * + * @param causeMatcher + * the matcher to matcher the outer throwable's inner cause with, which must not be {@code null} + */ + public static Matcher becauseOf(Matcher causeMatcher) { + return Throws.becauseOf(causeMatcher); + } } diff --git a/hamcrest/src/main/java/org/hamcrest/Throws.java b/hamcrest/src/main/java/org/hamcrest/Throws.java new file mode 100644 index 00000000..fa64b9eb --- /dev/null +++ b/hamcrest/src/main/java/org/hamcrest/Throws.java @@ -0,0 +1,100 @@ +package org.hamcrest; + +import static java.util.Objects.requireNonNull; +import static org.hamcrest.core.IsInstanceOf.instanceOf; + +/** + * @author Peter De Maeyer + */ +public final class Throws implements SelfDescribing { + + private final Matcher matcher; + + public Throws(Matcher throwableMatcher) { + requireNonNull(throwableMatcher); + this.matcher = new BaseMatcher() { + + @Override + public boolean matches(Object actual) { + return throwableMatcher.matches(actual); + } + + @Override + public void describeTo(Description description) { + description.appendText("throws "); + throwableMatcher.describeTo(description); + } + + @Override + public void describeMismatch(Object item, Description mismatchDescription) { + mismatchDescription.appendText("threw but "); + throwableMatcher.describeMismatch(item, mismatchDescription); + } + }; + } + + Matcher asMatcher() { + return matcher; + } + + @Override + public void describeTo(Description description) { + matcher.describeTo(description); + } + + public void describeMismatch(Description description) { + description.appendText("did not throw"); + } + + public static Matcher withMessage(Matcher messageMatcher) { + return new TypeSafeMatcher() { + + @Override + protected boolean matchesSafely(T throwable) { + return messageMatcher.matches(throwable.getMessage()); + } + + @Override + public void describeTo(Description description) { + description.appendText("with message "); + messageMatcher.describeTo(description); + } + + @Override + protected void describeMismatchSafely(T item, Description mismatchDescription) { + mismatchDescription.appendText("message "); + messageMatcher.describeMismatch(item.getMessage(), mismatchDescription); + } + }; + } + + public static Matcher becauseOf(Matcher causeMatcher) { + return new TypeSafeMatcher() { + + @Override + protected boolean matchesSafely(T throwable) { + return causeMatcher.matches(throwable.getCause()); + } + + @Override + public void describeTo(Description description) { + description.appendText("because of "); + causeMatcher.describeTo(description); + } + + @Override + protected void describeMismatchSafely(T item, Description mismatchDescription) { + mismatchDescription.appendText("cause "); + causeMatcher.describeMismatch(item.getCause(), mismatchDescription); + } + }; + } + + public static Throws doesThrow(Matcher throwableMatcher) { + return new Throws(throwableMatcher); + } + + public static Throws throwsInstanceOf(Class throwableType) { + return doesThrow(instanceOf(throwableType)); + } +} diff --git a/hamcrest/src/test/java/org/hamcrest/MatcherAssertTest.java b/hamcrest/src/test/java/org/hamcrest/MatcherAssertTest.java index 8d88daa8..3110d3fb 100644 --- a/hamcrest/src/test/java/org/hamcrest/MatcherAssertTest.java +++ b/hamcrest/src/test/java/org/hamcrest/MatcherAssertTest.java @@ -1,9 +1,12 @@ package org.hamcrest; +import java.io.IOException; +import javax.xml.stream.XMLStreamException; + import org.junit.Test; +import static org.hamcrest.Matchers.*; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.*; public final class MatcherAssertTest { @@ -96,4 +99,115 @@ public void describeMismatch(Object item, Description mismatchDescription) { canAssertSubtypes() { assertThat(1, equalTo((Number) 1)); } + + @Test public void + throwableIsOfMatchingInstance() { + assertThat( + () -> { throw new IllegalStateException(); }, + throwsInstanceOf(IllegalStateException.class) + ); + } + + @Test public void + throwableIsNotOfMatchingInstance() { + String endLine = System.lineSeparator(); + String expectedMessage = endLine + "Expected: throws an instance of java.io.IOException" + endLine + + " but: threw but is a java.lang.IllegalStateException"; + try { + assertThat( + () -> { throw new IllegalStateException(); }, + throwsInstanceOf(IOException.class) + ); + fail("should have failed"); + } + catch (AssertionError e) { + assertEquals(expectedMessage, e.getMessage()); + } + } + + @Test public void + throwableHasMatchingMessage() { + assertThat( + () -> { throw new Exception("message"); }, + doesThrow(withMessage(equalTo("message"))) + ); + } + + @Test public void + throwableDoesNotHaveMatchingMessage() { + String endLine = System.lineSeparator(); + String expectedMessage = endLine + "Expected: throws with message \"expected message\"" + endLine + + " but: threw but message was \"actual message\""; + try { + assertThat( + () -> { throw new Exception("actual message"); }, + doesThrow(withMessage("expected message")) + ); + fail("should have failed"); + } + catch (AssertionError e) { + assertEquals(expectedMessage, e.getMessage()); + } + } + + @Test public void + throwableExecutionDoesNotThrow() { + String endLine = System.lineSeparator(); + String expectedMessage = endLine + "Expected: throws an instance of java.lang.NoSuchMethodError" + + endLine + " but: did not throw"; + try { + assertThat( + () -> {}, // Do nothing + throwsInstanceOf(NoSuchMethodError.class) + ); + fail("should have failed"); + } + catch (AssertionError e) { + assertEquals(expectedMessage, e.getMessage()); + } + } + + @Test public void + throwableCauseMatches() { + assertThat( + () -> { throw new RuntimeException(new XMLStreamException()); }, + doesThrow(becauseOf(instanceOf(XMLStreamException.class))) + ); + } + + @Test public void + throwableCauseDoesNotMatch() { + String endLine = System.lineSeparator(); + String expectedMessage = endLine + "Expected: throws because of an instance of java.lang.NullPointerException" + + endLine + " but: threw but cause is a java.lang.IllegalArgumentException"; + try { + assertThat( + () -> { throw new RuntimeException(new IllegalArgumentException()); }, + doesThrow(becauseOf(instanceOf(NullPointerException.class))) + ); + fail("should have failed"); + } + catch (AssertionError e) { + assertEquals(expectedMessage, e.getMessage()); + } + } + + @Test public void + throwableExecutionDoesNotMatchWithCustomMessage() { + String endLine = System.lineSeparator(); + String expectedMessage = "Custom message" + + endLine + "Expected: throws an instance of java.lang.NullPointerException" + + endLine + " but: threw but is a java.lang.IllegalArgumentException"; + try { + assertThat( + "Custom message", + () -> { throw new IllegalArgumentException(); }, + throwsInstanceOf(NullPointerException.class) + ); + fail("should have failed"); + } + catch (AssertionError e) { + assertEquals(expectedMessage, e.getMessage()); + } + } } From 5058b286eef8b00d0c7cebd6f38f04b0e6a4f755 Mon Sep 17 00:00:00 2001 From: Peter De Maeyer Date: Sun, 21 Feb 2021 10:43:14 +0100 Subject: [PATCH 2/3] #329 Updated fix for Java 1.7 --- .../main/java/org/hamcrest/Executable.java | 1 - .../src/main/java/org/hamcrest/Throws.java | 6 +- .../java/org/hamcrest/MatcherAssertTest.java | 62 ++++++++++++++++--- 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/hamcrest/src/main/java/org/hamcrest/Executable.java b/hamcrest/src/main/java/org/hamcrest/Executable.java index fb964219..6dace114 100644 --- a/hamcrest/src/main/java/org/hamcrest/Executable.java +++ b/hamcrest/src/main/java/org/hamcrest/Executable.java @@ -1,6 +1,5 @@ package org.hamcrest; -@FunctionalInterface public interface Executable { void execute() throws Throwable; diff --git a/hamcrest/src/main/java/org/hamcrest/Throws.java b/hamcrest/src/main/java/org/hamcrest/Throws.java index fa64b9eb..f4fc94a5 100644 --- a/hamcrest/src/main/java/org/hamcrest/Throws.java +++ b/hamcrest/src/main/java/org/hamcrest/Throws.java @@ -10,7 +10,7 @@ public final class Throws implements SelfDescribing { private final Matcher matcher; - public Throws(Matcher throwableMatcher) { + public Throws(final Matcher throwableMatcher) { requireNonNull(throwableMatcher); this.matcher = new BaseMatcher() { @@ -46,7 +46,7 @@ public void describeMismatch(Description description) { description.appendText("did not throw"); } - public static Matcher withMessage(Matcher messageMatcher) { + public static Matcher withMessage(final Matcher messageMatcher) { return new TypeSafeMatcher() { @Override @@ -68,7 +68,7 @@ protected void describeMismatchSafely(T item, Description mismatchDescription) { }; } - public static Matcher becauseOf(Matcher causeMatcher) { + public static Matcher becauseOf(final Matcher causeMatcher) { return new TypeSafeMatcher() { @Override diff --git a/hamcrest/src/test/java/org/hamcrest/MatcherAssertTest.java b/hamcrest/src/test/java/org/hamcrest/MatcherAssertTest.java index 3110d3fb..a56f58f8 100644 --- a/hamcrest/src/test/java/org/hamcrest/MatcherAssertTest.java +++ b/hamcrest/src/test/java/org/hamcrest/MatcherAssertTest.java @@ -103,7 +103,12 @@ public void describeMismatch(Object item, Description mismatchDescription) { @Test public void throwableIsOfMatchingInstance() { assertThat( - () -> { throw new IllegalStateException(); }, + new Executable() { + @Override + public void execute() { + throw new IllegalStateException(); + } + }, throwsInstanceOf(IllegalStateException.class) ); } @@ -115,7 +120,12 @@ public void describeMismatch(Object item, Description mismatchDescription) { + " but: threw but is a java.lang.IllegalStateException"; try { assertThat( - () -> { throw new IllegalStateException(); }, + new Executable() { + @Override + public void execute() { + throw new IllegalStateException(); + } + }, throwsInstanceOf(IOException.class) ); fail("should have failed"); @@ -128,7 +138,12 @@ public void describeMismatch(Object item, Description mismatchDescription) { @Test public void throwableHasMatchingMessage() { assertThat( - () -> { throw new Exception("message"); }, + new Executable() { + @Override + public void execute() throws Exception { + throw new Exception("message"); + } + }, doesThrow(withMessage(equalTo("message"))) ); } @@ -140,7 +155,12 @@ public void describeMismatch(Object item, Description mismatchDescription) { + " but: threw but message was \"actual message\""; try { assertThat( - () -> { throw new Exception("actual message"); }, + new Executable() { + @Override + public void execute() throws Exception { + throw new Exception("actual message"); + } + }, doesThrow(withMessage("expected message")) ); fail("should have failed"); @@ -157,7 +177,12 @@ public void describeMismatch(Object item, Description mismatchDescription) { + endLine + " but: did not throw"; try { assertThat( - () -> {}, // Do nothing + new Executable() { + @Override + public void execute() { + // Do nothing + } + }, throwsInstanceOf(NoSuchMethodError.class) ); fail("should have failed"); @@ -169,9 +194,15 @@ public void describeMismatch(Object item, Description mismatchDescription) { @Test public void throwableCauseMatches() { + Matcher instanceOfMatcher = instanceOf(XMLStreamException.class); assertThat( - () -> { throw new RuntimeException(new XMLStreamException()); }, - doesThrow(becauseOf(instanceOf(XMLStreamException.class))) + new Executable() { + @Override + public void execute() { + throw new RuntimeException(new XMLStreamException()); + } + }, + doesThrow(becauseOf(instanceOfMatcher)) ); } @@ -181,9 +212,15 @@ public void describeMismatch(Object item, Description mismatchDescription) { String expectedMessage = endLine + "Expected: throws because of an instance of java.lang.NullPointerException" + endLine + " but: threw but cause is a java.lang.IllegalArgumentException"; try { + Matcher instanceOfMatcher = instanceOf(NullPointerException.class); assertThat( - () -> { throw new RuntimeException(new IllegalArgumentException()); }, - doesThrow(becauseOf(instanceOf(NullPointerException.class))) + new Executable() { + @Override + public void execute() { + throw new RuntimeException(new IllegalArgumentException()); + } + }, + doesThrow(becauseOf(instanceOfMatcher)) ); fail("should have failed"); } @@ -201,7 +238,12 @@ public void describeMismatch(Object item, Description mismatchDescription) { try { assertThat( "Custom message", - () -> { throw new IllegalArgumentException(); }, + new Executable() { + @Override + public void execute() { + throw new IllegalArgumentException(); + } + }, throwsInstanceOf(NullPointerException.class) ); fail("should have failed"); From c9906b54ae8b723f0aaa6a798e56d6318cb53484 Mon Sep 17 00:00:00 2001 From: Peter De Maeyer Date: Sun, 21 Feb 2021 20:51:08 +0100 Subject: [PATCH 3/3] 329 Replaced > with > in JavaDoc which was causing build failures with some JDKs --- hamcrest/src/main/java/org/hamcrest/Matchers.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hamcrest/src/main/java/org/hamcrest/Matchers.java b/hamcrest/src/main/java/org/hamcrest/Matchers.java index 32125b9b..1095e06a 100644 --- a/hamcrest/src/main/java/org/hamcrest/Matchers.java +++ b/hamcrest/src/main/java/org/hamcrest/Matchers.java @@ -1716,7 +1716,7 @@ public static org.hamcrest.Matcher hasXPath(java.lang.String x * Creates a {@link Throws} object that matches a throwable according to the given throwable matcher. * This can be used with the {@link MatcherAssert#assertThat(String, Executable, Throws)} family of methods. * For example: - *
assertThat(() -> methodCallThatThrowsException(), doesThrow(withMessage("file not found")))
+ *
assertThat(() -> methodCallThatThrowsException(), doesThrow(withMessage("file not found")))
* * @param throwableMatcher * the matcher for the throwable to match, which must not be {@code null} @@ -1729,7 +1729,7 @@ public static Throws doesThrow(MatcherassertThat(() -> methodCallThatThrowsIOException(), throwsInstanceOf(IOException.class)) + *
assertThat(() -> methodCallThatThrowsIOException(), throwsInstanceOf(IOException.class))
* This is shorthand for {@code doesThrow(instanceOf(MyThrowable.class))}, to be used as equivalent for JUnit 5's * {@code assertThrows(MyThrowable.class, () -> {})}. * @@ -1744,7 +1744,7 @@ public static Throws throwsInstanceOf(Class throwabl * Creates a matcher that matches a throwable by matching its message. * This can be used with the {@link MatcherAssert#assertThat(String, Executable, Throws)} family of methods. * For example: - *
assertThat(() -> methodCallThatThrowsException(), doesThrow(withMessage(startsWith("file not found"))))
+ *
assertThat(() -> methodCallThatThrowsException(), doesThrow(withMessage(startsWith("file not found"))))
* * @param messageMatcher * the matcher to match the throwable's message with, which must not be {@code null} @@ -1757,7 +1757,7 @@ public static Matcher withMessage(Matcher messa * Creates a matcher that matches a throwable by its message. * This can be used with the {@link MatcherAssert#assertThat(String, Executable, Throws)} family of methods. * For example: - *
assertThat(() -> methodCallThatThrowsException(), doesThrow(withMessage("message")))
+ *
assertThat(() -> methodCallThatThrowsException(), doesThrow(withMessage("message")))
* This is shorthand for {@code doesThrow(withMessage(equalTo("message")))}. * * @param messageToMatch @@ -1771,7 +1771,7 @@ public static Matcher withMessage(String messageToMatch * Creates a matcher that matches an outer throwable by matching its inner cause. * This can be used with the {@link MatcherAssert#assertThat(String, Executable, Throws)} family of methods. * For example: - *
assertThat(() -> methodCallThatThrowsInvocationTargetException(), doesThrow(becauseOf(instanceOf(IOException.class))))
+ *
assertThat(() -> methodCallThatThrowsInvocationTargetException(), doesThrow(becauseOf(instanceOf(IOException.class))))
* * @param causeMatcher * the matcher to matcher the outer throwable's inner cause with, which must not be {@code null}