Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added declarative equivalent of JUnit's assertThrows #330

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions hamcrest/src/main/java/org/hamcrest/Executable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.hamcrest;

public interface Executable {

void execute() throws Throwable;
}
29 changes: 28 additions & 1 deletion hamcrest/src/main/java/org/hamcrest/MatcherAssert.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,37 @@ public static <T> void assertThat(String reason, T actual, Matcher<? super T> ma
throw new AssertionError(description.toString());
}
}

public static void assertThat(String reason, boolean assertion) {
if (!assertion) {
throw new AssertionError(reason);
}
}

public static <T extends Throwable> void assertThat(Executable executable, Throws<T> doesThrow) {
assertThat("", executable, doesThrow);
}

public static <T extends Throwable> void assertThat(String reason, Executable executable, Throws<T> 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());
}
}
}
}
66 changes: 66 additions & 0 deletions hamcrest/src/main/java/org/hamcrest/Matchers.java
Original file line number Diff line number Diff line change
Expand Up @@ -1712,5 +1712,71 @@ public static org.hamcrest.Matcher<org.w3c.dom.Node> 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:
* <pre>assertThat(() -&gt; methodCallThatThrowsException(), doesThrow(withMessage("file not found")))</pre>
*
* @param throwableMatcher
* the matcher for the throwable to match, which must not be {@code null}
*/
public static <T extends Throwable> Throws<T> doesThrow(Matcher<? super Throwable> 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:
* <pre>assertThat(() -&gt; methodCallThatThrowsIOException(), throwsInstanceOf(IOException.class))</pre>
* 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 <T extends Throwable> Throws<T> throwsInstanceOf(Class<T> 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:
* <pre>assertThat(() -&gt; methodCallThatThrowsException(), doesThrow(withMessage(startsWith("file not found"))))</pre>
*
* @param messageMatcher
* the matcher to match the throwable's message with, which must not be {@code null}
*/
public static <T extends Throwable> Matcher<T> withMessage(Matcher<String> 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:
* <pre>assertThat(() -&gt; methodCallThatThrowsException(), doesThrow(withMessage("message")))</pre>
* 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 <T extends Throwable> Matcher<T> 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:
* <pre>assertThat(() -&gt; methodCallThatThrowsInvocationTargetException(), doesThrow(becauseOf(instanceOf(IOException.class))))</pre>
*
* @param causeMatcher
* the matcher to matcher the outer throwable's inner cause with, which must not be {@code null}
*/
public static <T extends Throwable> Matcher<T> becauseOf(Matcher<? extends Throwable> causeMatcher) {
return Throws.becauseOf(causeMatcher);
}
}
100 changes: 100 additions & 0 deletions hamcrest/src/main/java/org/hamcrest/Throws.java
Original file line number Diff line number Diff line change
@@ -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<T extends Throwable> implements SelfDescribing {

private final Matcher<? super T> matcher;

public Throws(final Matcher<? super T> throwableMatcher) {
requireNonNull(throwableMatcher);
this.matcher = new BaseMatcher<T>() {

@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<? super T> asMatcher() {
return matcher;
}

@Override
public void describeTo(Description description) {
matcher.describeTo(description);
}

public void describeMismatch(Description description) {
description.appendText("did not throw");
}

public static <T extends Throwable> Matcher<T> withMessage(final Matcher<String> messageMatcher) {
return new TypeSafeMatcher<T>() {

@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 <T extends Throwable> Matcher<T> becauseOf(final Matcher<? extends Throwable> causeMatcher) {
return new TypeSafeMatcher<T>() {

@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 <T extends Throwable> Throws<T> doesThrow(Matcher<? super T> throwableMatcher) {
return new Throws<T>(throwableMatcher);
}

public static <T extends Throwable> Throws<T> throwsInstanceOf(Class<T> throwableType) {
return doesThrow(instanceOf(throwableType));
}
}
158 changes: 157 additions & 1 deletion hamcrest/src/test/java/org/hamcrest/MatcherAssertTest.java
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -96,4 +99,157 @@ public void describeMismatch(Object item, Description mismatchDescription) {
canAssertSubtypes() {
assertThat(1, equalTo((Number) 1));
}

@Test public void
throwableIsOfMatchingInstance() {
assertThat(
new Executable() {
@Override
public void execute() {
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 <java.lang.IllegalStateException> is a java.lang.IllegalStateException";
try {
assertThat(
new Executable() {
@Override
public void execute() {
throw new IllegalStateException();
}
},
throwsInstanceOf(IOException.class)
);
fail("should have failed");
}
catch (AssertionError e) {
assertEquals(expectedMessage, e.getMessage());
}
}

@Test public void
throwableHasMatchingMessage() {
assertThat(
new Executable() {
@Override
public void execute() throws Exception {
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(
new Executable() {
@Override
public void execute() throws Exception {
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(
new Executable() {
@Override
public void execute() {
// Do nothing
}
},
throwsInstanceOf(NoSuchMethodError.class)
);
fail("should have failed");
}
catch (AssertionError e) {
assertEquals(expectedMessage, e.getMessage());
}
}

@Test public void
throwableCauseMatches() {
Matcher<? extends Throwable> instanceOfMatcher = instanceOf(XMLStreamException.class);
assertThat(
new Executable() {
@Override
public void execute() {
throw new RuntimeException(new XMLStreamException());
}
},
doesThrow(becauseOf(instanceOfMatcher))
);
}

@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 <java.lang.IllegalArgumentException> is a java.lang.IllegalArgumentException";
try {
Matcher<? extends Throwable> instanceOfMatcher = instanceOf(NullPointerException.class);
assertThat(
new Executable() {
@Override
public void execute() {
throw new RuntimeException(new IllegalArgumentException());
}
},
doesThrow(becauseOf(instanceOfMatcher))
);
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 <java.lang.IllegalArgumentException> is a java.lang.IllegalArgumentException";
try {
assertThat(
"Custom message",
new Executable() {
@Override
public void execute() {
throw new IllegalArgumentException();
}
},
throwsInstanceOf(NullPointerException.class)
);
fail("should have failed");
}
catch (AssertionError e) {
assertEquals(expectedMessage, e.getMessage());
}
}
}