Skip to content

Commit

Permalink
Add assertThrows overloads that take a ThrowingSupplier
Browse files Browse the repository at this point in the history
If the given ThrowingSupplier fails to throw the expected exception and
instead returns a value, the string representation of the value is
included in the failure message to aide debugging.
  • Loading branch information
cushon committed May 2, 2018
1 parent e28654c commit 7bd0a35
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ on GitHub.

==== New Features and Improvements

* ❓
* New `assertThrows` methods in `Assertions` provide a more specific failure
message if the lambda returns a result instead of throwing the expected
exception.


[[release-notes-5.3.0-M1-junit-vintage]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.function.Supplier;

import org.junit.jupiter.api.function.Executable;
import org.junit.jupiter.api.function.ThrowingSupplier;
import org.opentest4j.AssertionFailedError;

/**
Expand All @@ -34,25 +35,45 @@ private AssertThrows() {
}
///CLOVER:ON

static <T extends Throwable> T assertThrows(Class<T> expectedType, ThrowingSupplier<?> supplier) {
return assertThrows(expectedType, supplier::get, (Object) null);
}

static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable) {
return assertThrows(expectedType, executable, (Object) null);
return assertThrows(expectedType, asSupplier(executable), (Object) null);
}

static <T extends Throwable> T assertThrows(Class<T> expectedType, ThrowingSupplier<?> supplier, String message) {
return assertThrows(expectedType, supplier::get, (Object) message);
}

static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable, String message) {
return assertThrows(expectedType, executable, (Object) message);
return assertThrows(expectedType, asSupplier(executable), (Object) message);
}

static <T extends Throwable> T assertThrows(Class<T> expectedType, ThrowingSupplier<?> supplier,
Supplier<String> messageSupplier) {
return assertThrows(expectedType, supplier::get, (Object) messageSupplier);
}

static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable,
Supplier<String> messageSupplier) {
return assertThrows(expectedType, executable, (Object) messageSupplier);
return assertThrows(expectedType, asSupplier(executable), (Object) messageSupplier);
}

@SuppressWarnings("unchecked")
private static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable,
Object messageOrSupplier) {
return assertThrows(expectedType, asSupplier(executable), messageOrSupplier);
}

@SuppressWarnings("unchecked")
private static <T extends Throwable> T assertThrows(Class<T> expectedType, ThrowingResultSupplier<?> supplier,
Object messageOrSupplier) {

Object result;
try {
executable.execute();
result = supplier.get();
}
catch (Throwable actualException) {
if (expectedType.isInstance(actualException)) {
Expand All @@ -66,8 +87,34 @@ private static <T extends Throwable> T assertThrows(Class<T> expectedType, Execu
}

String message = buildPrefix(nullSafeGet(messageOrSupplier))
+ String.format("Expected %s to be thrown, but nothing was thrown.", getCanonicalName(expectedType));
+ String.format("Expected %s to be thrown, but nothing was thrown", getCanonicalName(expectedType))
+ (supplier.formatResult() ? String.format(" (returned %s).", result) : ".");
throw new AssertionFailedError(message);
}

private interface ThrowingResultSupplier<T> extends ThrowingSupplier<T> {
/**
* Returns true if the result should be included in the failure message in the case where the supplier
* returns a result instead of throwing the expected exception.
*/
default boolean formatResult() {
return true;
}
}

private static ThrowingResultSupplier<Void> asSupplier(Executable executable) {
return new ThrowingResultSupplier<Void>() {
@Override
public Void get() throws Throwable {
executable.execute();
return null;
}

@Override
public boolean formatResult() {
return false;
}
};
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -1169,6 +1169,23 @@ public static <T extends Throwable> T assertThrows(Class<T> expectedType, Execut
return AssertThrows.assertThrows(expectedType, executable);
}

/**
* <em>Asserts</em> that execution of the supplied {@code executable} throws
* an exception of the {@code expectedType} and returns the exception.
*
* <p>If no exception is thrown, or if an exception of a different type is
* thrown, this method will fail.
*
* <p>If you do not want to perform additional checks on the exception instance,
* simply ignore the return value.
*
* <p>If the given {@link ThrowingSupplier} returns a result instead of throwing the expected exception,
* the result will be included in the failure message.
*/
public static <T extends Throwable> T assertThrows(Class<T> expectedType, ThrowingSupplier<?> supplier) {
return AssertThrows.assertThrows(expectedType, supplier);
}

/**
* <em>Asserts</em> that execution of the supplied {@code executable} throws
* an exception of the {@code expectedType} and returns the exception.
Expand All @@ -1183,6 +1200,24 @@ public static <T extends Throwable> T assertThrows(Class<T> expectedType, Execut
return AssertThrows.assertThrows(expectedType, executable, message);
}

/**
* <em>Asserts</em> that execution of the supplied {@code executable} throws
* an exception of the {@code expectedType} and returns the exception.
*
* <p>If no exception is thrown, or if an exception of a different type is
* thrown, this method will fail.
*
* <p>If you do not want to perform additional checks on the exception instance,
* simply ignore the return value.
*
* <p>If the given {@link ThrowingSupplier} returns a result instead of throwing the expected exception,
* the result will be included in the failure message.
*/
public static <T extends Throwable> T assertThrows(Class<T> expectedType, ThrowingSupplier<?> supplier,
String message) {
return AssertThrows.assertThrows(expectedType, supplier, message);
}

/**
* <em>Asserts</em> that execution of the supplied {@code executable} throws
* an exception of the {@code expectedType} and returns the exception.
Expand All @@ -1201,6 +1236,27 @@ public static <T extends Throwable> T assertThrows(Class<T> expectedType, Execut
return AssertThrows.assertThrows(expectedType, executable, messageSupplier);
}

/**
* <em>Asserts</em> that execution of the supplied {@code executable} throws
* an exception of the {@code expectedType} and returns the exception.
*
* <p>If no exception is thrown, or if an exception of a different type is
* thrown, this method will fail.
*
* <p>If necessary, the failure message will be retrieved lazily from the
* supplied {@code messageSupplier}.
*
* <p>If you do not want to perform additional checks on the exception instance,
* simply ignore the return value.
*
* <p>If the given {@link ThrowingSupplier} returns a result instead of throwing the expected exception,
* the result will be included in the failure message.
*/
public static <T extends Throwable> T assertThrows(Class<T> expectedType, ThrowingSupplier<?> supplier,
Supplier<String> messageSupplier) {
return AssertThrows.assertThrows(expectedType, supplier, messageSupplier);
}

// --- executable ---

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
package org.junit.jupiter.api;

import static org.junit.jupiter.api.AssertionTestUtils.assertMessageContains;
import static org.junit.jupiter.api.AssertionTestUtils.assertMessageDoesNotContain;
import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals;
import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith;
import static org.junit.jupiter.api.AssertionTestUtils.expectAssertionFailedError;
Expand Down Expand Up @@ -235,6 +236,64 @@ void assertThrowsWithExecutableThatThrowsSameExceptionTypeFromDifferentClassLoad
}
}

@Test
void assertThrowsReturns() {
try {
assertThrows(EnigmaThrowable.class, () -> 42);
expectAssertionFailedError();
}
catch (AssertionFailedError ex) {
assertMessageContains(ex, "(returned 42)");
}
}

@Test
void assertThrowsReturnsNull() {
try {
assertThrows(EnigmaThrowable.class, () -> null);
expectAssertionFailedError();
}
catch (AssertionFailedError ex) {
assertMessageContains(ex, "(returned null)");
}
}

@Test
void assertThrowsReturnsVoid() {
try {
assertThrows(EnigmaThrowable.class, () -> {
});
expectAssertionFailedError();
}
catch (AssertionFailedError ex) {
assertMessageDoesNotContain(ex, "(returned null)");
}
}

@Test
void assertThrowsReturnsCustomMessage() {
try {
assertThrows(EnigmaThrowable.class, () -> 42, "custom message");
expectAssertionFailedError();
}
catch (AssertionFailedError ex) {
assertMessageContains(ex, "(returned 42)");
assertMessageContains(ex, "custom message");
}
}

@Test
void assertThrowsReturnsCustomMessageSupplier() {
try {
assertThrows(EnigmaThrowable.class, () -> 42, () -> "custom message");
expectAssertionFailedError();
}
catch (AssertionFailedError ex) {
assertMessageContains(ex, "(returned 42)");
assertMessageContains(ex, "custom message");
}
}

@SuppressWarnings("serial")
private static class LocalException extends RuntimeException {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ static void assertMessageContains(Throwable ex, String msg) throws AssertionErro
}
}

static void assertMessageDoesNotContain(Throwable ex, String msg) throws AssertionError {
if (ex.getMessage().contains(msg)) {
throw new AssertionError(
"Exception message should contain [" + msg + "], but was [" + ex.getMessage() + "].");
}
}

static void assertExpectedAndActualValues(AssertionFailedError ex, Object expected, Object actual)
throws AssertionError {
if (!wrapsEqualValue(ex.getExpected(), expected)) {
Expand Down

0 comments on commit 7bd0a35

Please sign in to comment.