Skip to content

Commit

Permalink
Alternate version of ThrowsException
Browse files Browse the repository at this point in the history
Co-authored-by: Guillermo Gutierrez Almazor <[email protected]>
  • Loading branch information
tumbarumba and ggalmazor committed Oct 29, 2024
1 parent 1bed2f5 commit 92a6e4b
Show file tree
Hide file tree
Showing 2 changed files with 183 additions and 0 deletions.
85 changes: 85 additions & 0 deletions hamcrest/src/main/java/org/hamcrest/exception/ThrowsException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package org.hamcrest.exception;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeDiagnosingMatcher;
import org.hamcrest.core.IsInstanceOf;

import static org.hamcrest.core.IsAnything.anything;
import static org.hamcrest.core.IsEqual.equalTo;

/**
* Tests if a Runnable throws a matching exception.
*
* @param <T> the type of the matched Runnable
*/
public class ThrowsException<T extends Runnable> extends TypeSafeDiagnosingMatcher<T> {
private final IsInstanceOf classMatcher;
private final Matcher<? super String> messageMatcher;

public ThrowsException(IsInstanceOf classMatcher, Matcher<? super String> messageMatcher) {
this.classMatcher = classMatcher;
this.messageMatcher = messageMatcher;
}

@Override
protected boolean matchesSafely(T runnable, Description mismatchDescription) {
try {
runnable.run();
mismatchDescription.appendText("the runnable didn't throw");
return false;
} catch (Throwable t) {
boolean classMatches = classMatcher.matches(t);
if (!classMatches) {
mismatchDescription.appendText("thrown exception class was ").appendText(t.getClass().getName());
}

boolean messageMatches = messageMatcher.matches(t.getMessage());
if (!messageMatches) {
if (!classMatches) {
mismatchDescription.appendText(" and the ");
}
mismatchDescription.appendText("thrown exception message ");
messageMatcher.describeMismatch(t.getMessage(), mismatchDescription);
}

return classMatches && messageMatches;
}
}

@Override
public void describeTo(Description description) {
description
.appendText("a runnable throwing ").appendDescriptionOf(classMatcher)
.appendText(" with message ").appendDescriptionOf(messageMatcher);
}

public static <T extends Runnable> ThrowsException<T> throwsException() {
return throwsException(Throwable.class);
}

public static <T extends Runnable, U extends Throwable> ThrowsException<T> throwsException(U throwableInstance) {
return throwsException(throwableInstance.getClass(), equalTo(throwableInstance.getMessage()));
}

public static <T extends Runnable, U extends Throwable> ThrowsException<T> throwsException(Class<U> throwableClass) {
return new ThrowsException<>(new IsInstanceOf(throwableClass), anything("<anything>"));
}

public static <T extends Runnable> ThrowsException<T> throwsExceptionWithMessage(String exactMessage) {
return throwsException(Throwable.class, equalTo(exactMessage));
}

public static <T extends Runnable> ThrowsException<T> throwsExceptionWithMessage(Matcher<String> messageMatcher) {
return throwsException(Throwable.class, messageMatcher);
}

public static <T extends Runnable, U extends Throwable> ThrowsException<T> throwsException(Class<U> throwableClass, String exactMessage) {
return throwsException(throwableClass, equalTo(exactMessage));
}

public static <T extends Runnable, U extends Throwable> ThrowsException<T> throwsException(Class<U> throwableClass, Matcher<String> messageMatcher) {
return new ThrowsException<>(new IsInstanceOf(throwableClass), messageMatcher);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package org.hamcrest.exception;

import org.junit.jupiter.api.Test;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.exception.ThrowsException.throwsException;
import static org.hamcrest.exception.ThrowsException.throwsExceptionWithMessage;
import static org.hamcrest.test.MatcherAssertions.*;

class ThrowsExceptionTest {
@Test
public void usageExamplesForThrowsException() {
Runnable runnableThatWillThrow = new ThrowingRunnable(new TestException("boom"));

assertThat(runnableThatWillThrow, throwsException());
assertThat(runnableThatWillThrow, throwsException(new TestException("boom")));
assertThat(runnableThatWillThrow, throwsException(TestException.class));
assertThat(runnableThatWillThrow, throwsException(RuntimeException.class));
assertThat(runnableThatWillThrow, throwsException(RuntimeException.class, "boom"));
assertThat(runnableThatWillThrow, throwsException(TestException.class, containsString("oo")));
assertThat(runnableThatWillThrow, throwsExceptionWithMessage("boom"));
assertThat(runnableThatWillThrow, throwsExceptionWithMessage(containsString("oo")));
}

@Test
public void testThrowsException() {
assertMatches(
throwsException(new IllegalArgumentException("Boom!")),
runnableThrowing(new IllegalArgumentException("Boom!"))
);

assertDescription(
"a runnable throwing an instance of java.lang.IllegalArgumentException with message \"Boom!\"",
throwsException(new IllegalArgumentException("Boom!"))
);

assertMismatchDescription(
"thrown exception message was \"Bang!\"",
throwsException(new IllegalArgumentException("Boom!")),
runnableThrowing(new IllegalArgumentException("Bang!"))
);

assertMismatchDescription(
"thrown exception class was java.lang.NullPointerException",
throwsException(new IllegalArgumentException("Boom!")),
runnableThrowing(new NullPointerException("Boom!"))
);

assertMismatchDescription(
"thrown exception class was java.lang.IllegalArgumentException and the thrown exception message was \"Different message\"",
throwsException(new TestException("Boom!")),
runnableThrowing(new IllegalArgumentException("Different message"))
);

assertMismatchDescription(
"the runnable didn't throw",
throwsException(new IllegalArgumentException("Boom!")),
(Runnable) () -> {}
);
}

@Test
public void matchesIfRunnableThrowsSubclassedException() {
assertMatches(
throwsException(new RuntimeException("Boom!")),
runnableThrowing(new TestException("Boom!"))
);
}

public static class TestException extends RuntimeException {
public TestException(String message) {
super(message);
}
}

private Runnable runnableThrowing(Throwable exception) {
return new ThrowingRunnable(exception);
}

static class ThrowingRunnable implements Runnable {
private final Throwable throwable;

ThrowingRunnable(Throwable throwable) {
this.throwable = throwable;
}

@Override
public void run() {
sneakyThrow(throwable);
}

@SuppressWarnings("unchecked")
private <T extends Throwable> void sneakyThrow(Throwable throwable) throws T {
throw (T) throwable;
}
}
}

0 comments on commit 92a6e4b

Please sign in to comment.