
com.github.robtimus.junit.support.AdditionalAssertions Maven / Gradle / Ivy
/*
* AdditionalAssertions.java
* Copyright 2021 Rob Spoor
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.robtimus.junit.support;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.function.Executable;
import org.junit.platform.commons.util.StringUtils;
import org.junit.platform.commons.util.UnrecoverableExceptions;
import org.opentest4j.AssertionFailedError;
/**
* A collection of utility methods that support asserting conditions in tests, in addition to what is already provided by {@link Assertions}.
*
* @author Rob Spoor
*/
@SuppressWarnings("nls")
public final class AdditionalAssertions {
private AdditionalAssertions() {
}
/**
* Asserts that the supplied throwable has a direct cause of the given type, and returns this cause.
*
* If the throwable has no cause, or the direct cause has a different type, this method will fail.
*
* If you do not want to perform additional checks on the cause instance, ignore the return value.
*
* @param The expected cause type.
* @param expectedType The expected cause type.
* @param throwable The throwable to check.
* @return The nearest cause of the given exception with the given type.
*/
public static T assertHasDirectCause(Class expectedType, Throwable throwable) {
return assertHasDirectCause(expectedType, throwable, (Object) null);
}
/**
* Asserts that the supplied throwable has a cause, directly or indirectly, of the given type, and returns this cause.
*
* If the throwable has no cause, or the direct cause has a different type, this method will fail.
*
* If you do not want to perform additional checks on the cause instance, ignore the return value.
*
* @param The expected cause type.
* @param expectedType The expected cause type.
* @param throwable The throwable to check.
* @param message The failure message to fail with.
* @return The nearest cause of the given exception with the given type.
*/
public static T assertHasDirectCause(Class expectedType, Throwable throwable, String message) {
return assertHasDirectCause(expectedType, throwable, (Object) message);
}
/**
* Asserts that the supplied throwable has a cause, directly or indirectly, of the given type, and returns this cause.
*
* If the throwable has no cause, or the direct cause has a different type, this method will fail.
*
* If you do not want to perform additional checks on the cause instance, ignore the return value.
*
* @param The expected cause type.
* @param expectedType The expected cause type.
* @param throwable The throwable to check.
* @param messageSupplier The supplier for the failure message to fail with.
* @return The nearest cause of the given exception with the given type.
*/
public static T assertHasDirectCause(Class expectedType, Throwable throwable, Supplier messageSupplier) {
return assertHasDirectCause(expectedType, throwable, (Object) messageSupplier);
}
private static T assertHasDirectCause(Class expectedType, Throwable throwable, Object messageOrSupplier) {
Throwable cause = throwable.getCause();
if (expectedType.isInstance(cause)) {
return expectedType.cast(cause);
}
String message = String.format("%sexpected caused by %s but was: <%s>",
buildPrefix(nullSafeGet(messageOrSupplier)),
formatClass(expectedType),
cause);
throw new AssertionFailedError(message);
}
/**
* Asserts that the supplied throwable has a cause, directly or indirectly, of the given type, and returns this cause.
*
* If the throwable has no cause, or has no direct or indirect cause of the given type, this method will fail.
*
* If you do not want to perform additional checks on the cause instance, ignore the return value.
*
* @param The expected cause type.
* @param expectedType The expected cause type.
* @param throwable The throwable to check.
* @return The nearest cause of the given exception with the given type.
*/
public static T assertHasCause(Class expectedType, Throwable throwable) {
return assertHasCause(expectedType, throwable, (Object) null);
}
/**
* Asserts that the supplied throwable has a cause, directly or indirectly, of the given type, and returns this cause.
*
* If the throwable has no cause, or has no direct or indirect cause of the given type, this method will fail.
*
* If you do not want to perform additional checks on the cause instance, ignore the return value.
*
* @param The expected cause type.
* @param expectedType The expected cause type.
* @param throwable The throwable to check.
* @param message The failure message to fail with.
* @return The nearest cause of the given exception with the given type.
*/
public static T assertHasCause(Class expectedType, Throwable throwable, String message) {
return assertHasCause(expectedType, throwable, (Object) message);
}
/**
* Asserts that the supplied throwable has a cause, directly or indirectly, of the given type, and returns this cause.
*
* If the throwable has no cause, or has no direct or indirect cause of the given type, this method will fail.
*
* If you do not want to perform additional checks on the cause instance, ignore the return value.
*
* @param The expected cause type.
* @param expectedType The expected cause type.
* @param throwable The throwable to check.
* @param messageSupplier The supplier for the failure message to fail with.
* @return The nearest cause of the given exception with the given type.
*/
public static T assertHasCause(Class expectedType, Throwable throwable, Supplier messageSupplier) {
return assertHasCause(expectedType, throwable, (Object) messageSupplier);
}
private static T assertHasCause(Class expectedType, Throwable throwable, Object messageOrSupplier) {
List causes = new ArrayList<>();
Throwable cause = throwable.getCause();
while (cause != null) {
if (expectedType.isInstance(cause)) {
return expectedType.cast(cause);
}
causes.add("<" + cause + ">");
cause = cause.getCause();
}
String message = String.format("%sexpected caused by %s but was: %s",
buildPrefix(nullSafeGet(messageOrSupplier)),
formatClass(expectedType),
causes);
throw new AssertionFailedError(message);
}
/**
* Asserts that execution of the supplied {@link Executable} throws an exception of exactly one of the supplied types,
* and returns the exception.
*
* If no exception is thrown, or if an exception of a different type is thrown, this method will fail.
*
* If you do not want to perform additional checks on the exception instance, ignore the return value.
*
* @param The common super type of the expected exception types.
* @param expectedType1 The first expected exception type to check for.
* @param expectedType2 The second expected exception type to check for.
* @param executable The {@link Executable} to run.
* @return The exception that was thrown.
* @see Assertions#assertThrowsExactly(Class, Executable)
*/
public static T assertThrowsExactlyOneOf(Class extends T> expectedType1, Class extends T> expectedType2,
Executable executable) {
List> expectedTypes = Arrays.asList(expectedType1, expectedType2);
return assertThrowsExactlyOneOf(expectedTypes, executable);
}
/**
* Asserts that execution of the supplied {@link Executable} throws an exception of exactly one of the supplied types,
* and returns the exception.
*
* If no exception is thrown, or if an exception of a different type is thrown, this method will fail.
*
* If you do not want to perform additional checks on the exception instance, ignore the return value.
*
* @param The common super type of the expected exception types.
* @param expectedType1 The first expected exception type to check for.
* @param expectedType2 The second expected exception type to check for.
* @param executable The {@link Executable} to run.
* @param message The failure message to fail with.
* @return The exception that was thrown.
* @see Assertions#assertThrowsExactly(Class, Executable, String)
*/
public static T assertThrowsExactlyOneOf(Class extends T> expectedType1, Class extends T> expectedType2,
Executable executable, String message) {
List> expectedTypes = Arrays.asList(expectedType1, expectedType2);
return assertThrowsExactlyOneOf(expectedTypes, executable, message);
}
/**
* Asserts that execution of the supplied {@link Executable} throws an exception of exactly one of the supplied types,
* and returns the exception.
*
* If no exception is thrown, or if an exception of a different type is thrown, this method will fail.
*
* If you do not want to perform additional checks on the exception instance, ignore the return value.
*
* @param The common super type of the expected exception types.
* @param expectedType1 The first expected exception type to check for.
* @param expectedType2 The second expected exception type to check for.
* @param executable The {@link Executable} to run.
* @param messageSupplier The supplier for the failure message to fail with.
* @return The exception that was thrown.
* @see Assertions#assertThrowsExactly(Class, Executable, Supplier)
*/
public static T assertThrowsExactlyOneOf(Class extends T> expectedType1, Class extends T> expectedType2,
Executable executable, Supplier messageSupplier) {
List> expectedTypes = Arrays.asList(expectedType1, expectedType2);
return assertThrowsExactlyOneOf(expectedTypes, executable, messageSupplier);
}
/**
* Asserts that execution of the supplied {@link Executable} throws an exception of exactly one of the supplied types,
* and returns the exception.
*
* If no exception is thrown, or if an exception of a different type is thrown, this method will fail.
*
* If you do not want to perform additional checks on the exception instance, ignore the return value.
*
* @param The common super type of the expected exception types.
* @param expectedTypes The expected exception types to check for.
* @param executable The {@link Executable} to run.
* @return The exception that was thrown.
* @see Assertions#assertThrowsExactly(Class, Executable)
*/
public static T assertThrowsExactlyOneOf(Collection extends Class extends T>> expectedTypes, Executable executable) {
return assertThrowsExactlyOneOf(expectedTypes, executable, (Object) null);
}
/**
* Asserts that execution of the supplied {@link Executable} throws an exception of exactly one of the supplied types,
* and returns the exception.
*
* If no exception is thrown, or if an exception of a different type is thrown, this method will fail.
*
* If you do not want to perform additional checks on the exception instance, ignore the return value.
*
* @param The common super type of the expected exception types.
* @param expectedTypes The expected exception types to check for.
* @param executable The {@link Executable} to run.
* @param message The failure message to fail with.
* @return The exception that was thrown.
* @see Assertions#assertThrowsExactly(Class, Executable, String)
*/
public static T assertThrowsExactlyOneOf(Collection extends Class extends T>> expectedTypes, Executable executable,
String message) {
return assertThrowsExactlyOneOf(expectedTypes, executable, (Object) message);
}
/**
* Asserts that execution of the supplied {@link Executable} throws an exception of exactly one of the supplied types,
* and returns the exception.
*
* If no exception is thrown, or if an exception of a different type is thrown, this method will fail.
*
* If you do not want to perform additional checks on the exception instance, ignore the return value.
*
* @param The common super type of the expected exception types.
* @param expectedTypes The expected exception types to check for.
* @param executable The {@link Executable} to run.
* @param messageSupplier The supplier for the failure message to fail with.
* @return The exception that was thrown.
* @see Assertions#assertThrowsExactly(Class, Executable, Supplier)
*/
public static T assertThrowsExactlyOneOf(Collection extends Class extends T>> expectedTypes, Executable executable,
Supplier messageSupplier) {
return assertThrowsExactlyOneOf(expectedTypes, executable, (Object) messageSupplier);
}
private static T assertThrowsExactlyOneOf(Collection extends Class extends T>> expectedTypes, Executable executable,
Object messageOrSupplier) {
try {
executable.execute();
} catch (Throwable actualException) {
for (Class extends T> expectedType : expectedTypes) {
if (expectedType.equals(actualException.getClass())) {
return expectedType.cast(actualException);
}
}
UnrecoverableExceptions.rethrowIfUnrecoverable(actualException);
String message = String.format("%s%sexpected: one of %s but was: %s",
buildPrefix(nullSafeGet(messageOrSupplier)),
buildPrefix("Unexpected exception type thrown"),
formatClasses(expectedTypes),
formatClass(actualException.getClass()));
throw new AssertionFailedError(message, actualException);
}
String message = String.format("%sExpected one of %s to be thrown, but nothing was thrown.",
buildPrefix(nullSafeGet(messageOrSupplier)),
formatClasses(expectedTypes));
throw new AssertionFailedError(message);
}
/**
* Asserts that execution of the supplied {@link Executable} throws an exception of one of the supplied types, and returns the exception.
*
* If no exception is thrown, or if an exception of a different type is thrown, this method will fail.
*
* If you do not want to perform additional checks on the exception instance, ignore the return value.
*
* @param The common super type of the expected exception types.
* @param expectedType1 The first expected exception type to check for.
* @param expectedType2 The second expected exception type to check for.
* @param executable The {@link Executable} to run.
* @return The exception that was thrown.
* @see Assertions#assertThrows(Class, Executable)
*/
public static T assertThrowsOneOf(Class extends T> expectedType1, Class extends T> expectedType2,
Executable executable) {
List> expectedTypes = Arrays.asList(expectedType1, expectedType2);
return assertThrowsOneOf(expectedTypes, executable);
}
/**
* Asserts that execution of the supplied {@link Executable} throws an exception of one of the supplied types, and returns the exception.
*
* If no exception is thrown, or if an exception of a different type is thrown, this method will fail.
*
* If you do not want to perform additional checks on the exception instance, ignore the return value.
*
* @param The common super type of the expected exception types.
* @param expectedType1 The first expected exception type to check for.
* @param expectedType2 The second expected exception type to check for.
* @param executable The {@link Executable} to run.
* @param message The failure message to fail with.
* @return The exception that was thrown.
* @see Assertions#assertThrows(Class, Executable, String)
*/
public static T assertThrowsOneOf(Class extends T> expectedType1, Class extends T> expectedType2,
Executable executable, String message) {
List> expectedTypes = Arrays.asList(expectedType1, expectedType2);
return assertThrowsOneOf(expectedTypes, executable, message);
}
/**
* Asserts that execution of the supplied {@link Executable} throws an exception of one of the supplied types, and returns the exception.
*
* If no exception is thrown, or if an exception of a different type is thrown, this method will fail.
*
* If you do not want to perform additional checks on the exception instance, ignore the return value.
*
* @param The common super type of the expected exception types.
* @param expectedType1 The first expected exception type to check for.
* @param expectedType2 The second expected exception type to check for.
* @param executable The {@link Executable} to run.
* @param messageSupplier The supplier for the failure message to fail with.
* @return The exception that was thrown.
* @see Assertions#assertThrows(Class, Executable, Supplier)
*/
public static T assertThrowsOneOf(Class extends T> expectedType1, Class extends T> expectedType2,
Executable executable, Supplier messageSupplier) {
List> expectedTypes = Arrays.asList(expectedType1, expectedType2);
return assertThrowsOneOf(expectedTypes, executable, messageSupplier);
}
/**
* Asserts that execution of the supplied {@link Executable} throws an exception of one of the supplied types, and returns the exception.
*
* If no exception is thrown, or if an exception of a different type is thrown, this method will fail.
*
* If you do not want to perform additional checks on the exception instance, ignore the return value.
*
* @param The common super type of the expected exception types.
* @param expectedTypes The expected exception types to check for.
* @param executable The {@link Executable} to run.
* @return The exception that was thrown.
* @see Assertions#assertThrows(Class, Executable)
*/
public static T assertThrowsOneOf(Collection extends Class extends T>> expectedTypes, Executable executable) {
return assertThrowsOneOf(expectedTypes, executable, (Object) null);
}
/**
* Asserts that execution of the supplied {@link Executable} throws an exception of one of the supplied types, and returns the exception.
*
* If no exception is thrown, or if an exception of a different type is thrown, this method will fail.
*
* If you do not want to perform additional checks on the exception instance, ignore the return value.
*
* @param The common super type of the expected exception types.
* @param expectedTypes The expected exception types to check for.
* @param executable The {@link Executable} to run.
* @param message The failure message to fail with.
* @return The exception that was thrown.
* @see Assertions#assertThrows(Class, Executable, String)
*/
public static T assertThrowsOneOf(Collection extends Class extends T>> expectedTypes, Executable executable,
String message) {
return assertThrowsOneOf(expectedTypes, executable, (Object) message);
}
/**
* Asserts that execution of the supplied {@link Executable} throws an exception of one of the supplied types, and returns the exception.
*
* If no exception is thrown, or if an exception of a different type is thrown, this method will fail.
*
* If you do not want to perform additional checks on the exception instance, ignore the return value.
*
* @param The common super type of the expected exception types.
* @param expectedTypes The expected exception types to check for.
* @param executable The {@link Executable} to run.
* @param messageSupplier The supplier for the failure message to fail with.
* @return The exception that was thrown.
* @see Assertions#assertThrows(Class, Executable, Supplier)
*/
public static T assertThrowsOneOf(Collection extends Class extends T>> expectedTypes, Executable executable,
Supplier messageSupplier) {
return assertThrowsOneOf(expectedTypes, executable, (Object) messageSupplier);
}
private static T assertThrowsOneOf(Collection extends Class extends T>> expectedTypes, Executable executable,
Object messageOrSupplier) {
try {
executable.execute();
} catch (Throwable actualException) {
for (Class extends T> expectedType : expectedTypes) {
if (expectedType.isInstance(actualException)) {
return expectedType.cast(actualException);
}
}
UnrecoverableExceptions.rethrowIfUnrecoverable(actualException);
String message = String.format("%s%sexpected: one of %s but was: %s",
buildPrefix(nullSafeGet(messageOrSupplier)),
buildPrefix("Unexpected exception type thrown"),
formatClasses(expectedTypes),
formatClass(actualException.getClass()));
throw new AssertionFailedError(message, actualException);
}
String message = String.format("%sExpected one of %s to be thrown, but nothing was thrown.",
buildPrefix(nullSafeGet(messageOrSupplier)),
formatClasses(expectedTypes));
throw new AssertionFailedError(message);
}
private static String formatClasses(Collection extends Class>> classes) {
return classes.stream()
.map(AdditionalAssertions::formatClass)
.collect(Collectors.joining(", "));
}
private static String formatClass(Class> clazz) {
String canonicalName = clazz.getCanonicalName();
return "<" + (canonicalName != null ? canonicalName : clazz.getName()) + ">";
}
// Copy of JUnit's own methods
private static String nullSafeGet(Object messageOrSupplier) {
if (messageOrSupplier instanceof String) {
return (String) messageOrSupplier;
}
if (messageOrSupplier instanceof Supplier>) {
Object message = ((Supplier>) messageOrSupplier).get();
if (message != null) {
return message.toString();
}
}
return null;
}
private static String buildPrefix(String message) {
return StringUtils.isNotBlank(message) ? message + " ==> " : "";
}
}