From e91e5dc26b86d0fc0a92f747f0b5dcdc9e5fbec2 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 17 Jul 2022 18:27:51 +0200 Subject: [PATCH] Introduce AssertionFailureBuilder (#2972) Resolves #2967. --- .../docs/asciidoc/release-notes/index.adoc | 2 + .../release-notes/release-notes-5.9.0.adoc | 59 +++++ .../junit/jupiter/api/AssertArrayEquals.java | 34 +-- .../junit/jupiter/api/AssertDoesNotThrow.java | 11 +- .../org/junit/jupiter/api/AssertEquals.java | 9 +- .../org/junit/jupiter/api/AssertFalse.java | 18 +- .../junit/jupiter/api/AssertInstanceOf.java | 16 +- .../jupiter/api/AssertIterableEquals.java | 34 +-- .../junit/jupiter/api/AssertLinesMatch.java | 12 +- .../junit/jupiter/api/AssertNotEquals.java | 31 +-- .../org/junit/jupiter/api/AssertNotNull.java | 12 +- .../org/junit/jupiter/api/AssertNotSame.java | 13 +- .../org/junit/jupiter/api/AssertNull.java | 21 +- .../org/junit/jupiter/api/AssertSame.java | 14 +- .../org/junit/jupiter/api/AssertThrows.java | 24 +- .../jupiter/api/AssertThrowsExactly.java | 23 +- .../org/junit/jupiter/api/AssertTimeout.java | 23 +- .../org/junit/jupiter/api/AssertTrue.java | 18 +- .../jupiter/api/AssertionFailureBuilder.java | 205 ++++++++++++++++++ .../org/junit/jupiter/api/AssertionUtils.java | 83 ------- .../api/AssertInstanceOfAssertionsTests.java | 2 +- .../api/AssertThrowsAssertionsTests.java | 55 +++-- .../AssertThrowsExactlyAssertionsTests.java | 72 +++--- .../junit/jupiter/api/AssertionTestUtils.java | 7 - 24 files changed, 516 insertions(+), 282 deletions(-) create mode 100644 documentation/src/docs/asciidoc/release-notes/release-notes-5.9.0.adoc create mode 100644 junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionFailureBuilder.java diff --git a/documentation/src/docs/asciidoc/release-notes/index.adoc b/documentation/src/docs/asciidoc/release-notes/index.adoc index a1306096ed1..6cca6088e47 100644 --- a/documentation/src/docs/asciidoc/release-notes/index.adoc +++ b/documentation/src/docs/asciidoc/release-notes/index.adoc @@ -16,6 +16,8 @@ authors as well as build tool and IDE vendors. include::{includedir}/link-attributes.adoc[] +include::{basedir}/release-notes-5.9.0.adoc[] + include::{basedir}/release-notes-5.9.0-RC1.adoc[] include::{basedir}/release-notes-5.9.0-M1.adoc[] diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.0.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.0.adoc new file mode 100644 index 00000000000..9d09d864276 --- /dev/null +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.9.0.adoc @@ -0,0 +1,59 @@ +[[release-notes-5.9.0]] +== 5.9.0 + +*Date of Release:* ❓ + +*Scope:* ❓ + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit5-repo}+/milestone/62?closed=1+[5.9.0] milestone page in the JUnit repository on +GitHub. + + +[[release-notes-5.9.0-junit-platform]] +=== JUnit Platform + +==== Bug Fixes + +* ❓ + +==== Deprecations and Breaking Changes + +* ❓ + +==== New Features and Improvements + +* ❓ + + +[[release-notes-5.9.0-junit-jupiter]] +=== JUnit Jupiter + +==== Bug Fixes + +* ❓ + +==== Deprecations and Breaking Changes + +* ❓ + +==== New Features and Improvements + +* `AssertionFailureBuilder` allows reusing Jupiter's logic for creating failure messages + to assist in writing custom assertion methods. + + +[[release-notes-5.9.0-junit-vintage]] +=== JUnit Vintage + +==== Bug Fixes + +* ❓ + +==== Deprecations and Breaking Changes + +* ❓ + +==== New Features and Improvements + +* ❓ diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertArrayEquals.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertArrayEquals.java index 0f28b904e66..2a2f8f6d204 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertArrayEquals.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertArrayEquals.java @@ -10,11 +10,8 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; -import static org.junit.jupiter.api.AssertionUtils.fail; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.jupiter.api.AssertionUtils.formatIndexes; -import static org.junit.jupiter.api.AssertionUtils.formatValues; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; import static org.junit.platform.commons.util.ReflectionUtils.isArray; import java.util.ArrayDeque; @@ -406,30 +403,41 @@ private static void assertArraysNotNull(Object expected, Object actual, Deque indexes, Object messageOrSupplier) { - fail(buildPrefix(nullSafeGet(messageOrSupplier)) + "expected array was " + formatIndexes(indexes)); + assertionFailure() // + .message(messageOrSupplier) // + .reason("expected array was " + formatIndexes(indexes)) // + .buildAndThrow(); } private static void failActualArrayIsNull(Deque indexes, Object messageOrSupplier) { - fail(buildPrefix(nullSafeGet(messageOrSupplier)) + "actual array was " + formatIndexes(indexes)); + assertionFailure() // + .message(messageOrSupplier) // + .reason("actual array was " + formatIndexes(indexes)) // + .buildAndThrow(); } private static void assertArraysHaveSameLength(int expected, int actual, Deque indexes, Object messageOrSupplier) { if (expected != actual) { - String prefix = buildPrefix(nullSafeGet(messageOrSupplier)); - String message = "array lengths differ" + formatIndexes(indexes) + ", expected: <" + expected - + "> but was: <" + actual + ">"; - fail(prefix + message); + assertionFailure() // + .message(messageOrSupplier) // + .reason("array lengths differ" + formatIndexes(indexes)) // + .expected(expected) // + .actual(actual) // + .buildAndThrow(); } } private static void failArraysNotEqual(Object expected, Object actual, Deque indexes, Object messageOrSupplier) { - String prefix = buildPrefix(nullSafeGet(messageOrSupplier)); - String message = "array contents differ" + formatIndexes(indexes) + ", " + formatValues(expected, actual); - fail(prefix + message); + assertionFailure() // + .message(messageOrSupplier) // + .reason("array contents differ" + formatIndexes(indexes)) // + .expected(expected) // + .actual(actual) // + .buildAndThrow(); } private static Deque nullSafeIndexes(Deque indexes, int newIndex) { diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertDoesNotThrow.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertDoesNotThrow.java index e64b136772f..d11a3e0e6db 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertDoesNotThrow.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertDoesNotThrow.java @@ -10,8 +10,7 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.util.function.Supplier; @@ -78,9 +77,11 @@ private static T assertDoesNotThrow(ThrowingSupplier supplier, Object mes } private static AssertionFailedError createAssertionFailedError(Object messageOrSupplier, Throwable t) { - String message = buildPrefix(nullSafeGet(messageOrSupplier)) + "Unexpected exception thrown: " - + t.getClass().getName() + buildSuffix(t.getMessage()); - return new AssertionFailedError(message, t); + return assertionFailure() // + .message(messageOrSupplier) // + .reason("Unexpected exception thrown: " + t.getClass().getName() + buildSuffix(t.getMessage())) // + .cause(t) // + .build(); } private static String buildSuffix(String message) { diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertEquals.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertEquals.java index 8a06b335599..9afd4b1072c 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertEquals.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertEquals.java @@ -10,8 +10,8 @@ package org.junit.jupiter.api; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.jupiter.api.AssertionUtils.doublesAreEqual; -import static org.junit.jupiter.api.AssertionUtils.failNotEqual; import static org.junit.jupiter.api.AssertionUtils.floatsAreEqual; import static org.junit.jupiter.api.AssertionUtils.objectsAreEqual; @@ -189,4 +189,11 @@ static void assertEquals(Object expected, Object actual, Supplier messag } } + private static void failNotEqual(Object expected, Object actual, Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .expected(expected) // + .actual(actual) // + .buildAndThrow(); + } } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java index 604c26e98af..8364cb64615 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java @@ -10,9 +10,7 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; -import static org.junit.jupiter.api.AssertionUtils.fail; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.util.function.BooleanSupplier; import java.util.function.Supplier; @@ -25,8 +23,6 @@ */ class AssertFalse { - private static final String EXPECTED_FALSE = "expected: but was: "; - private AssertFalse() { /* no-op */ } @@ -37,13 +33,13 @@ static void assertFalse(boolean condition) { static void assertFalse(boolean condition, String message) { if (condition) { - fail(buildPrefix(message) + EXPECTED_FALSE, false, true); + failNotFalse(message); } } static void assertFalse(boolean condition, Supplier messageSupplier) { if (condition) { - fail(buildPrefix(nullSafeGet(messageSupplier)) + EXPECTED_FALSE, false, true); + failNotFalse(messageSupplier); } } @@ -59,4 +55,12 @@ static void assertFalse(BooleanSupplier booleanSupplier, Supplier messag assertFalse(booleanSupplier.getAsBoolean(), messageSupplier); } + private static void failNotFalse(Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .expected(false) // + .actual(true) // + .buildAndThrow(); + } + } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java index fd491bc25c0..f1891d138c8 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java @@ -10,14 +10,10 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; -import static org.junit.jupiter.api.AssertionUtils.format; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.util.function.Supplier; -import org.opentest4j.AssertionFailedError; - /** * {@code AssertInstanceOf} is a collection of utility methods that support * asserting that an object is of an expected type — in other words, if it @@ -45,10 +41,12 @@ static T assertInstanceOf(Class expectedType, Object actualValue, Supplie private static T assertInstanceOf(Class expectedType, Object actualValue, Object messageOrSupplier) { if (!expectedType.isInstance(actualValue)) { - String reason = (actualValue == null ? "Unexpected null value" : "Unexpected type"); - String message = buildPrefix(nullSafeGet(messageOrSupplier)) - + format(expectedType, actualValue == null ? null : actualValue.getClass(), reason); - throw new AssertionFailedError(message); + assertionFailure() // + .message(messageOrSupplier) // + .reason(actualValue == null ? "Unexpected null value" : "Unexpected type") // + .expected(expectedType) // + .actual(actualValue == null ? null : actualValue.getClass()) // + .buildAndThrow(); } return expectedType.cast(actualValue); } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertIterableEquals.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertIterableEquals.java index dd581398d4d..a569d2105b9 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertIterableEquals.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertIterableEquals.java @@ -10,11 +10,8 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; -import static org.junit.jupiter.api.AssertionUtils.fail; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.jupiter.api.AssertionUtils.formatIndexes; -import static org.junit.jupiter.api.AssertionUtils.formatValues; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; import java.util.ArrayDeque; import java.util.Deque; @@ -139,11 +136,17 @@ private static void assertIterablesNotNull(Object expected, Object actual, Deque } private static void failExpectedIterableIsNull(Deque indexes, Object messageOrSupplier) { - fail(buildPrefix(nullSafeGet(messageOrSupplier)) + "expected iterable was " + formatIndexes(indexes)); + assertionFailure() // + .message(messageOrSupplier) // + .reason("expected iterable was " + formatIndexes(indexes)) // + .buildAndThrow(); } private static void failActualIterableIsNull(Deque indexes, Object messageOrSupplier) { - fail(buildPrefix(nullSafeGet(messageOrSupplier)) + "actual iterable was " + formatIndexes(indexes)); + assertionFailure() // + .message(messageOrSupplier) // + .reason("actual iterable was " + formatIndexes(indexes)) // + .buildAndThrow(); } private static void assertIteratorsAreEmpty(Iterator expected, Iterator actual, int processed, @@ -156,19 +159,24 @@ private static void assertIteratorsAreEmpty(Iterator expected, Iterator ac AtomicInteger actualCount = new AtomicInteger(processed); actual.forEachRemaining(e -> actualCount.incrementAndGet()); - String prefix = buildPrefix(nullSafeGet(messageOrSupplier)); - String message = "iterable lengths differ" + formatIndexes(indexes) + ", expected: <" + expectedCount.get() - + "> but was: <" + actualCount.get() + ">"; - fail(prefix + message); + assertionFailure() // + .message(messageOrSupplier) // + .reason("iterable lengths differ" + formatIndexes(indexes)) // + .expected(expectedCount.get()) // + .actual(actualCount.get()) // + .buildAndThrow(); } } private static void failIterablesNotEqual(Object expected, Object actual, Deque indexes, Object messageOrSupplier) { - String prefix = buildPrefix(nullSafeGet(messageOrSupplier)); - String message = "iterable contents differ" + formatIndexes(indexes) + ", " + formatValues(expected, actual); - fail(prefix + message); + assertionFailure() // + .message(messageOrSupplier) // + .reason("iterable contents differ" + formatIndexes(indexes)) // + .expected(expected) // + .actual(actual) // + .buildAndThrow(); } private final static class Pair { diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertLinesMatch.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertLinesMatch.java index 8b55d54d12e..1479728830e 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertLinesMatch.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertLinesMatch.java @@ -12,8 +12,7 @@ import static java.lang.String.format; import static java.lang.String.join; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.platform.commons.util.Preconditions.condition; import static org.junit.platform.commons.util.Preconditions.notNull; @@ -195,8 +194,13 @@ String snippet(String line) { void fail(String format, Object... args) { String newLine = System.lineSeparator(); - String message = buildPrefix(nullSafeGet(messageOrSupplier)) + format(format, args); - AssertionUtils.fail(message, join(newLine, expectedLines), join(newLine, actualLines)); + assertionFailure() // + .message(messageOrSupplier) // + .reason(format(format, args)) // + .expected(join(newLine, expectedLines)) // + .actual(join(newLine, actualLines)) // + .includeValuesInMessage(false) // + .buildAndThrow(); } } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotEquals.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotEquals.java index 3bdb087dd9f..1e8be26c6a5 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotEquals.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotEquals.java @@ -10,11 +10,9 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.jupiter.api.AssertionUtils.doublesAreEqual; -import static org.junit.jupiter.api.AssertionUtils.fail; import static org.junit.jupiter.api.AssertionUtils.floatsAreEqual; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; import static org.junit.jupiter.api.AssertionUtils.objectsAreEqual; import java.util.function.Supplier; @@ -52,7 +50,7 @@ static void assertNotEquals(byte unexpected, byte actual, String message) { */ static void assertNotEquals(byte unexpected, byte actual, Supplier messageSupplier) { if (unexpected == actual) { - failEqual(actual, nullSafeGet(messageSupplier)); + failEqual(actual, messageSupplier); } } @@ -77,7 +75,7 @@ static void assertNotEquals(short unexpected, short actual, String message) { */ static void assertNotEquals(short unexpected, short actual, Supplier messageSupplier) { if (unexpected == actual) { - failEqual(actual, nullSafeGet(messageSupplier)); + failEqual(actual, messageSupplier); } } @@ -102,7 +100,7 @@ static void assertNotEquals(int unexpected, int actual, String message) { */ static void assertNotEquals(int unexpected, int actual, Supplier messageSupplier) { if (unexpected == actual) { - failEqual(actual, nullSafeGet(messageSupplier)); + failEqual(actual, messageSupplier); } } @@ -127,7 +125,7 @@ static void assertNotEquals(long unexpected, long actual, String message) { */ static void assertNotEquals(long unexpected, long actual, Supplier messageSupplier) { if (unexpected == actual) { - failEqual(actual, nullSafeGet(messageSupplier)); + failEqual(actual, messageSupplier); } } @@ -152,7 +150,7 @@ static void assertNotEquals(float unexpected, float actual, String message) { */ static void assertNotEquals(float unexpected, float actual, Supplier messageSupplier) { if (floatsAreEqual(unexpected, actual)) { - failEqual(actual, nullSafeGet(messageSupplier)); + failEqual(actual, messageSupplier); } } @@ -177,7 +175,7 @@ static void assertNotEquals(float unexpected, float actual, float delta, String */ static void assertNotEquals(float unexpected, float actual, float delta, Supplier messageSupplier) { if (floatsAreEqual(unexpected, actual, delta)) { - failEqual(actual, nullSafeGet(messageSupplier)); + failEqual(actual, messageSupplier); } } @@ -202,7 +200,7 @@ static void assertNotEquals(double unexpected, double actual, String message) { */ static void assertNotEquals(double unexpected, double actual, Supplier messageSupplier) { if (doublesAreEqual(unexpected, actual)) { - failEqual(actual, nullSafeGet(messageSupplier)); + failEqual(actual, messageSupplier); } } @@ -227,7 +225,7 @@ static void assertNotEquals(double unexpected, double actual, double delta, Stri */ static void assertNotEquals(double unexpected, double actual, double delta, Supplier messageSupplier) { if (doublesAreEqual(unexpected, actual, delta)) { - failEqual(actual, nullSafeGet(messageSupplier)); + failEqual(actual, messageSupplier); } } @@ -252,7 +250,7 @@ static void assertNotEquals(char unexpected, char actual, String message) { */ static void assertNotEquals(char unexpected, char actual, Supplier messageSupplier) { if (unexpected == actual) { - failEqual(actual, nullSafeGet(messageSupplier)); + failEqual(actual, messageSupplier); } } @@ -268,12 +266,15 @@ static void assertNotEquals(Object unexpected, Object actual, String message) { static void assertNotEquals(Object unexpected, Object actual, Supplier messageSupplier) { if (objectsAreEqual(unexpected, actual)) { - failEqual(actual, nullSafeGet(messageSupplier)); + failEqual(actual, messageSupplier); } } - private static void failEqual(Object actual, String message) { - fail(buildPrefix(message) + "expected: not equal but was: <" + actual + ">"); + private static void failEqual(Object actual, Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .reason("expected: not equal but was: <" + actual + ">") // + .buildAndThrow(); } } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java index eebd9329cf4..71be7b88a72 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java @@ -10,8 +10,7 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.util.function.Supplier; @@ -39,11 +38,14 @@ static void assertNotNull(Object actual, String message) { static void assertNotNull(Object actual, Supplier messageSupplier) { if (actual == null) { - failNull(nullSafeGet(messageSupplier)); + failNull(messageSupplier); } } - private static void failNull(String message) { - Assertions.fail(buildPrefix(message) + "expected: not "); + private static void failNull(Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .reason("expected: not ") // + .buildAndThrow(); } } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotSame.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotSame.java index a3652c39292..753e734c2a3 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotSame.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotSame.java @@ -10,9 +10,7 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; -import static org.junit.jupiter.api.AssertionUtils.fail; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.util.function.Supplier; @@ -40,12 +38,15 @@ static void assertNotSame(Object unexpected, Object actual, String message) { static void assertNotSame(Object unexpected, Object actual, Supplier messageSupplier) { if (unexpected == actual) { - failSame(actual, nullSafeGet(messageSupplier)); + failSame(actual, messageSupplier); } } - private static void failSame(Object actual, String message) { - fail(buildPrefix(message) + "expected: not same but was: <" + actual + ">"); + private static void failSame(Object actual, Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .reason("expected: not same but was: <" + actual + ">") // + .buildAndThrow(); } } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java index b3a43b24844..4a9e92b2d4a 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java @@ -10,10 +10,7 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; -import static org.junit.jupiter.api.AssertionUtils.fail; -import static org.junit.jupiter.api.AssertionUtils.format; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.util.function.Supplier; @@ -41,18 +38,16 @@ static void assertNull(Object actual, String message) { static void assertNull(Object actual, Supplier messageSupplier) { if (actual != null) { - failNotNull(actual, nullSafeGet(messageSupplier)); + failNotNull(actual, messageSupplier); } } - private static void failNotNull(Object actual, String message) { - String stringRepresentation = actual.toString(); - if (stringRepresentation == null || stringRepresentation.equals("null")) { - fail(format(null, actual, message), null, actual); - } - else { - fail(buildPrefix(message) + "expected: but was: <" + actual + ">", null, actual); - } + private static void failNotNull(Object actual, Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .expected(null) // + .actual(actual) // + .buildAndThrow(); } } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertSame.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertSame.java index 6f8a790dc23..c080b3e01f6 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertSame.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertSame.java @@ -10,9 +10,7 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.fail; -import static org.junit.jupiter.api.AssertionUtils.format; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.util.function.Supplier; @@ -40,12 +38,16 @@ static void assertSame(Object expected, Object actual, String message) { static void assertSame(Object expected, Object actual, Supplier messageSupplier) { if (expected != actual) { - failNotSame(expected, actual, nullSafeGet(messageSupplier)); + failNotSame(expected, actual, messageSupplier); } } - private static void failNotSame(Object expected, Object actual, String message) { - fail(format(expected, actual, message), expected, actual); + private static void failNotSame(Object expected, Object actual, Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .expected(expected) // + .actual(actual) // + .buildAndThrow(); } } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrows.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrows.java index 9be0d9a2f1d..d4e8b19d349 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrows.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrows.java @@ -10,16 +10,14 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; -import static org.junit.jupiter.api.AssertionUtils.format; +import static java.lang.String.format; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.jupiter.api.AssertionUtils.getCanonicalName; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; import java.util.function.Supplier; import org.junit.jupiter.api.function.Executable; import org.junit.platform.commons.util.UnrecoverableExceptions; -import org.opentest4j.AssertionFailedError; /** * {@code AssertThrows} is a collection of utility methods that support asserting @@ -60,15 +58,19 @@ private static T assertThrows(Class expectedType, Execu } else { UnrecoverableExceptions.rethrowIfUnrecoverable(actualException); - String message = buildPrefix(nullSafeGet(messageOrSupplier)) - + format(expectedType, actualException.getClass(), "Unexpected exception type thrown"); - throw new AssertionFailedError(message, actualException); + throw assertionFailure() // + .message(messageOrSupplier) // + .expected(expectedType) // + .actual(actualException.getClass()) // + .reason("Unexpected exception type thrown") // + .cause(actualException) // + .build(); } } - - String message = buildPrefix(nullSafeGet(messageOrSupplier)) - + String.format("Expected %s to be thrown, but nothing was thrown.", getCanonicalName(expectedType)); - throw new AssertionFailedError(message); + throw assertionFailure() // + .message(messageOrSupplier) // + .reason(format("Expected %s to be thrown, but nothing was thrown.", getCanonicalName(expectedType))) // + .build(); } } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrowsExactly.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrowsExactly.java index f40a05f273f..39a4c216936 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrowsExactly.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrowsExactly.java @@ -10,16 +10,14 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; -import static org.junit.jupiter.api.AssertionUtils.format; +import static java.lang.String.format; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.jupiter.api.AssertionUtils.getCanonicalName; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; import java.util.function.Supplier; import org.junit.jupiter.api.function.Executable; import org.junit.platform.commons.util.UnrecoverableExceptions; -import org.opentest4j.AssertionFailedError; /** * {@code AssertThrowsExactly} is a collection of utility methods that support asserting @@ -60,15 +58,20 @@ private static T assertThrowsExactly(Class expectedType } else { UnrecoverableExceptions.rethrowIfUnrecoverable(actualException); - String message = buildPrefix(nullSafeGet(messageOrSupplier)) - + format(expectedType, actualException.getClass(), "Unexpected exception type thrown"); - throw new AssertionFailedError(message, actualException); + throw assertionFailure() // + .message(messageOrSupplier) // + .expected(expectedType) // + .actual(actualException.getClass()) // + .reason("Unexpected exception type thrown") // + .cause(actualException) // + .build(); } } - String message = buildPrefix(nullSafeGet(messageOrSupplier)) - + String.format("Expected %s to be thrown, but nothing was thrown.", getCanonicalName(expectedType)); - throw new AssertionFailedError(message); + throw assertionFailure() // + .message(messageOrSupplier) // + .reason(format("Expected %s to be thrown, but nothing was thrown.", getCanonicalName(expectedType))) // + .build(); } } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java index d5634c578d3..81268f7eafe 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java @@ -10,9 +10,7 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.time.Duration; import java.util.concurrent.ExecutionException; @@ -30,7 +28,6 @@ import org.junit.jupiter.api.function.ThrowingSupplier; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.ExceptionUtils; -import org.opentest4j.AssertionFailedError; /** * {@code AssertTimeout} is a collection of utility methods that support asserting @@ -87,8 +84,11 @@ private static T assertTimeout(Duration timeout, ThrowingSupplier supplie long timeElapsed = System.currentTimeMillis() - start; if (timeElapsed > timeoutInMillis) { - fail(buildPrefix(nullSafeGet(messageOrSupplier)) + "execution exceeded timeout of " + timeoutInMillis - + " ms by " + (timeElapsed - timeoutInMillis) + " ms"); + assertionFailure() // + .message(messageOrSupplier) // + .reason("execution exceeded timeout of " + timeoutInMillis + " ms by " + + (timeElapsed - timeoutInMillis) + " ms") // + .buildAndThrow(); } return result; } @@ -147,19 +147,18 @@ private static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplie return future.get(timeoutInMillis, TimeUnit.MILLISECONDS); } catch (TimeoutException ex) { - String message = buildPrefix(nullSafeGet(messageOrSupplier)) + "execution timed out after " - + timeoutInMillis + " ms"; + AssertionFailureBuilder failure = assertionFailure() // + .message(messageOrSupplier) // + .reason("execution timed out after " + timeoutInMillis + " ms"); Thread thread = threadReference.get(); if (thread != null) { ExecutionTimeoutException exception = new ExecutionTimeoutException( "Execution timed out in thread " + thread.getName()); exception.setStackTrace(thread.getStackTrace()); - throw new AssertionFailedError(message, exception); - } - else { - throw new AssertionFailedError(message); + failure.cause(exception); } + throw failure.build(); } catch (ExecutionException ex) { throw ExceptionUtils.throwAsUncheckedException(ex.getCause()); diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java index 92e4451db0f..3d250a46f68 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java @@ -10,9 +10,7 @@ package org.junit.jupiter.api; -import static org.junit.jupiter.api.AssertionUtils.buildPrefix; -import static org.junit.jupiter.api.AssertionUtils.fail; -import static org.junit.jupiter.api.AssertionUtils.nullSafeGet; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import java.util.function.BooleanSupplier; import java.util.function.Supplier; @@ -25,8 +23,6 @@ */ class AssertTrue { - private static final String EXPECTED_TRUE = "expected: but was: "; - private AssertTrue() { /* no-op */ } @@ -37,13 +33,13 @@ static void assertTrue(boolean condition) { static void assertTrue(boolean condition, String message) { if (!condition) { - fail(buildPrefix(message) + EXPECTED_TRUE, true, false); + failNotTrue(message); } } static void assertTrue(boolean condition, Supplier messageSupplier) { if (!condition) { - fail(buildPrefix(nullSafeGet(messageSupplier)) + EXPECTED_TRUE, true, false); + failNotTrue(messageSupplier); } } @@ -59,4 +55,12 @@ static void assertTrue(BooleanSupplier booleanSupplier, Supplier message assertTrue(booleanSupplier.getAsBoolean(), messageSupplier); } + private static void failNotTrue(Object messageOrSupplier) { + assertionFailure() // + .message(messageOrSupplier) // + .expected(true) // + .actual(false) // + .buildAndThrow(); + } + } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionFailureBuilder.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionFailureBuilder.java new file mode 100644 index 00000000000..b1df82fab85 --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionFailureBuilder.java @@ -0,0 +1,205 @@ +/* + * Copyright 2015-2022 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import static org.apiguardian.api.API.Status.STABLE; +import static org.junit.jupiter.api.AssertionUtils.getCanonicalName; + +import java.util.function.Supplier; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.StringUtils; +import org.opentest4j.AssertionFailedError; + +/** + * Builder for {@link AssertionFailedError AssertionFailedErrors}. + *

+ * Using this builder ensures consistency in how failure message are formatted + * within JUnit Jupiter and for custom user-defined assertions. + * + * @since 5.9 + * @see AssertionFailedError + */ +@API(status = STABLE, since = "5.9") +public class AssertionFailureBuilder { + + private Object message; + private Throwable cause; + private boolean mismatch; + private Object expected; + private Object actual; + private String reason; + private boolean includeValuesInMessage = true; + + /** + * Create a new {@code AssertionFailureBuilder}. + */ + public static AssertionFailureBuilder assertionFailure() { + return new AssertionFailureBuilder(); + } + + private AssertionFailureBuilder() { + } + + /** + * Set the user-defined message of the assertion. + *

+ * The {@code message} may be passed as a {@link Supplier} or plain + * {@link String}. If any other type is passed, it is converted to + * {@code String} as per {@link StringUtils#nullSafeToString(Object)}. + * + * @param message the user-defined failure message; may be {@code null} + * @return this builder for method chaining + */ + public AssertionFailureBuilder message(Object message) { + this.message = message; + return this; + } + + /** + * Set the reason why the assertion failed. + * + * @param reason the failure reason; may be {@code null} + * @return this builder for method chaining + */ + public AssertionFailureBuilder reason(String reason) { + this.reason = reason; + return this; + } + + /** + * Set the cause of the assertion failure. + * + * @param cause the failure cause; may be {@code null} + * @return this builder for method chaining + */ + public AssertionFailureBuilder cause(Throwable cause) { + this.cause = cause; + return this; + } + + /** + * Set the expected value of the assertion. + * + * @param expected the expected value; may be {@code null} + * @return this builder for method chaining + */ + public AssertionFailureBuilder expected(Object expected) { + this.mismatch = true; + this.expected = expected; + return this; + } + + /** + * Set the actual value of the assertion. + * + * @param actual the actual value; may be {@code null} + * @return this builder for method chaining + */ + public AssertionFailureBuilder actual(Object actual) { + this.mismatch = true; + this.actual = actual; + return this; + } + + /** + * Set whether to include the actual and expected values in the generated + * failure message. + * + * @param includeValuesInMessage whether to include the actual and expected + * values + * @return this builder for method chaining + */ + public AssertionFailureBuilder includeValuesInMessage(boolean includeValuesInMessage) { + this.includeValuesInMessage = includeValuesInMessage; + return this; + } + + /** + * Build the {@link AssertionFailedError AssertionFailedError} and throw it. + * + * @throws AssertionFailedError always + */ + public void buildAndThrow() throws AssertionFailedError { + throw build(); + } + + /** + * Build the {@link AssertionFailedError AssertionFailedError} without + * throwing it. + * + * @return the built assertion failure + */ + public AssertionFailedError build() { + String reason = nullSafeGet(this.reason); + if (mismatch && includeValuesInMessage) { + reason = (reason == null ? "" : reason + ", ") + formatValues(expected, actual); + } + String message = nullSafeGet(this.message); + if (reason != null) { + message = buildPrefix(message) + reason; + } + return mismatch // + ? new AssertionFailedError(message, expected, actual, cause) // + : new AssertionFailedError(message, cause); + } + + private static String nullSafeGet(Object messageOrSupplier) { + if (messageOrSupplier == null) { + return null; + } + if (messageOrSupplier instanceof Supplier) { + Object message = ((Supplier) messageOrSupplier).get(); + return StringUtils.nullSafeToString(message); + } + return StringUtils.nullSafeToString(messageOrSupplier); + } + + private static String buildPrefix(String message) { + return (StringUtils.isNotBlank(message) ? message + " ==> " : ""); + } + + private static String formatValues(Object expected, Object actual) { + String expectedString = toString(expected); + String actualString = toString(actual); + if (expectedString.equals(actualString)) { + return String.format("expected: %s but was: %s", formatClassAndValue(expected, expectedString), + formatClassAndValue(actual, actualString)); + } + return String.format("expected: <%s> but was: <%s>", expectedString, actualString); + } + + private static String formatClassAndValue(Object value, String valueString) { + // If the value is null, return instead of null. + if (value == null) { + return ""; + } + String classAndHash = getClassName(value) + toHash(value); + // if it's a class, there's no need to repeat the class name contained in the valueString. + return (value instanceof Class ? "<" + classAndHash + ">" : classAndHash + "<" + valueString + ">"); + } + + private static String toString(Object obj) { + if (obj instanceof Class) { + return getCanonicalName((Class) obj); + } + return StringUtils.nullSafeToString(obj); + } + + private static String toHash(Object obj) { + return (obj == null ? "" : "@" + Integer.toHexString(System.identityHashCode(obj))); + } + + private static String getClassName(Object obj) { + return (obj == null ? "null" + : obj instanceof Class ? getCanonicalName((Class) obj) : obj.getClass().getName()); + } +} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java index 524f8142901..c591c217dad 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java @@ -15,7 +15,6 @@ import java.util.Deque; import java.util.function.Supplier; -import org.junit.platform.commons.util.StringUtils; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.opentest4j.AssertionFailedError; @@ -51,52 +50,10 @@ static void fail(Supplier messageSupplier) { throw new AssertionFailedError(nullSafeGet(messageSupplier)); } - static void fail(String message, Object expected, Object actual) { - throw new AssertionFailedError(message, expected, actual); - } - - /** - * Typically used for {@code assertEquals()}. - */ - static void failNotEqual(Object expected, Object actual, String message) { - fail(format(expected, actual, message), expected, actual); - } - - /** - * Typically used for {@code assertEquals()}. - */ - static void failNotEqual(Object expected, Object actual, Supplier messageSupplier) { - fail(format(expected, actual, nullSafeGet(messageSupplier)), expected, actual); - } - static String nullSafeGet(Supplier messageSupplier) { return (messageSupplier != null ? messageSupplier.get() : null); } - /** - * Alternative to {@link #nullSafeGet(Supplier)} that is used to avoid - * wrapping a String in a lambda expression. - * - * @param messageOrSupplier an object that is either a {@code String} or - * {@code Supplier} - */ - 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; - } - - static String buildPrefix(String message) { - return (StringUtils.isNotBlank(message) ? message + " ==> " : ""); - } - static String getCanonicalName(Class clazz) { try { String canonicalName = clazz.getCanonicalName(); @@ -108,46 +65,6 @@ static String getCanonicalName(Class clazz) { } } - static String format(Object expected, Object actual, String message) { - return buildPrefix(message) + formatValues(expected, actual); - } - - static String formatValues(Object expected, Object actual) { - String expectedString = toString(expected); - String actualString = toString(actual); - if (expectedString.equals(actualString)) { - return String.format("expected: %s but was: %s", formatClassAndValue(expected, expectedString), - formatClassAndValue(actual, actualString)); - } - return String.format("expected: <%s> but was: <%s>", expectedString, actualString); - } - - private static String formatClassAndValue(Object value, String valueString) { - // If the value is null, return instead of null. - if (value == null) { - return ""; - } - String classAndHash = getClassName(value) + toHash(value); - // if it's a class, there's no need to repeat the class name contained in the valueString. - return (value instanceof Class ? "<" + classAndHash + ">" : classAndHash + "<" + valueString + ">"); - } - - private static String toString(Object obj) { - if (obj instanceof Class) { - return getCanonicalName((Class) obj); - } - return StringUtils.nullSafeToString(obj); - } - - private static String toHash(Object obj) { - return (obj == null ? "" : "@" + Integer.toHexString(System.identityHashCode(obj))); - } - - private static String getClassName(Object obj) { - return (obj == null ? "null" - : obj instanceof Class ? getCanonicalName((Class) obj) : obj.getClass().getName()); - } - static String formatIndexes(Deque indexes) { if (indexes == null || indexes.isEmpty()) { return ""; diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertInstanceOfAssertionsTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertInstanceOfAssertionsTests.java index bdf0b8476f7..12ba2fa92f9 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertInstanceOfAssertionsTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertInstanceOfAssertionsTests.java @@ -93,7 +93,7 @@ private void assertInstanceOfSucceeds(Class expectedType, Object actualVa private void assertInstanceOfFails(Class expectedType, Object actualValue, String unexpectedSort) { String valueType = actualValue == null ? "null" : actualValue.getClass().getCanonicalName(); - String expectedMessage = String.format("Unexpected %s ==> expected: <%s> but was: <%s>", unexpectedSort, + String expectedMessage = String.format("Unexpected %s, expected: <%s> but was: <%s>", unexpectedSort, expectedType.getCanonicalName(), valueType); assertThrowsWithMessage(expectedMessage, () -> assertInstanceOf(expectedType, actualValue)); diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsAssertionsTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsAssertionsTests.java index 5239f0f1c94..302914794a3 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsAssertionsTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsAssertionsTests.java @@ -10,6 +10,7 @@ package org.junit.jupiter.api; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageContains; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageEquals; import static org.junit.jupiter.api.AssertionTestUtils.assertMessageStartsWith; @@ -19,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.IOException; +import java.io.Serial; import java.net.URL; import java.net.URLClassLoader; import java.util.concurrent.ExecutionException; @@ -32,6 +34,7 @@ * * @since 5.0 */ +@SuppressWarnings("ExcessiveLambdaUsage") class AssertThrowsAssertionsTests { private static final Executable nix = () -> { @@ -63,7 +66,7 @@ void assertThrowsWithMethodReferenceForVoidReturnType() { @Test void assertThrowsWithExecutableThatThrowsThrowable() { - EnigmaThrowable enigmaThrowable = assertThrows(EnigmaThrowable.class, (Executable) () -> { + EnigmaThrowable enigmaThrowable = assertThrows(EnigmaThrowable.class, () -> { throw new EnigmaThrowable(); }); assertNotNull(enigmaThrowable); @@ -71,7 +74,7 @@ void assertThrowsWithExecutableThatThrowsThrowable() { @Test void assertThrowsWithExecutableThatThrowsThrowableWithMessage() { - EnigmaThrowable enigmaThrowable = assertThrows(EnigmaThrowable.class, (Executable) () -> { + EnigmaThrowable enigmaThrowable = assertThrows(EnigmaThrowable.class, () -> { throw new EnigmaThrowable(); }, "message"); assertNotNull(enigmaThrowable); @@ -79,7 +82,7 @@ void assertThrowsWithExecutableThatThrowsThrowableWithMessage() { @Test void assertThrowsWithExecutableThatThrowsThrowableWithMessageSupplier() { - EnigmaThrowable enigmaThrowable = assertThrows(EnigmaThrowable.class, (Executable) () -> { + EnigmaThrowable enigmaThrowable = assertThrows(EnigmaThrowable.class, () -> { throw new EnigmaThrowable(); }, () -> "message"); assertNotNull(enigmaThrowable); @@ -87,7 +90,7 @@ void assertThrowsWithExecutableThatThrowsThrowableWithMessageSupplier() { @Test void assertThrowsWithExecutableThatThrowsCheckedException() { - IOException exception = assertThrows(IOException.class, (Executable) () -> { + IOException exception = assertThrows(IOException.class, () -> { throw new IOException(); }); assertNotNull(exception); @@ -95,7 +98,7 @@ void assertThrowsWithExecutableThatThrowsCheckedException() { @Test void assertThrowsWithExecutableThatThrowsRuntimeException() { - IllegalStateException illegalStateException = assertThrows(IllegalStateException.class, (Executable) () -> { + IllegalStateException illegalStateException = assertThrows(IllegalStateException.class, () -> { throw new IllegalStateException(); }); assertNotNull(illegalStateException); @@ -104,7 +107,7 @@ void assertThrowsWithExecutableThatThrowsRuntimeException() { @Test void assertThrowsWithExecutableThatThrowsError() { StackOverflowError stackOverflowError = assertThrows(StackOverflowError.class, - (Executable) AssertionTestUtils::recurseIndefinitely); + AssertionTestUtils::recurseIndefinitely); assertNotNull(stackOverflowError); } @@ -146,88 +149,92 @@ void assertThrowsWithExecutableThatDoesNotThrowAnExceptionWithMessageSupplier() @Test void assertThrowsWithExecutableThatThrowsAnUnexpectedException() { try { - assertThrows(IllegalStateException.class, (Executable) () -> { + assertThrows(IllegalStateException.class, () -> { throw new NumberFormatException(); }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "Unexpected exception type thrown ==> "); + assertMessageStartsWith(ex, "Unexpected exception type thrown, "); assertMessageContains(ex, "expected: "); assertMessageContains(ex, "but was: "); + assertThat(ex).hasCauseInstanceOf(NumberFormatException.class); } } @Test void assertThrowsWithExecutableThatThrowsAnUnexpectedExceptionWithMessageString() { try { - assertThrows(IllegalStateException.class, (Executable) () -> { + assertThrows(IllegalStateException.class, () -> { throw new NumberFormatException(); }, "Custom message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { // Should look something like this: - // Custom message ==> Unexpected exception type thrown ==> expected: but was: + // Custom message ==> Unexpected exception type thrown, expected: but was: assertMessageStartsWith(ex, "Custom message ==> "); - assertMessageContains(ex, "Unexpected exception type thrown ==> "); + assertMessageContains(ex, "Unexpected exception type thrown, "); assertMessageContains(ex, "expected: "); assertMessageContains(ex, "but was: "); + assertThat(ex).hasCauseInstanceOf(NumberFormatException.class); } } @Test void assertThrowsWithExecutableThatThrowsAnUnexpectedExceptionWithMessageSupplier() { try { - assertThrows(IllegalStateException.class, (Executable) () -> { + assertThrows(IllegalStateException.class, () -> { throw new NumberFormatException(); }, () -> "Custom message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { // Should look something like this: - // Custom message ==> Unexpected exception type thrown ==> expected: but was: + // Custom message ==> Unexpected exception type thrown, expected: but was: assertMessageStartsWith(ex, "Custom message ==> "); - assertMessageContains(ex, "Unexpected exception type thrown ==> "); + assertMessageContains(ex, "Unexpected exception type thrown, "); assertMessageContains(ex, "expected: "); assertMessageContains(ex, "but was: "); + assertThat(ex).hasCauseInstanceOf(NumberFormatException.class); } } @Test - @SuppressWarnings("serial") void assertThrowsWithExecutableThatThrowsInstanceOfAnonymousInnerClassAsUnexpectedException() { try { - assertThrows(IllegalStateException.class, (Executable) () -> { + assertThrows(IllegalStateException.class, () -> { throw new NumberFormatException() { }; }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "Unexpected exception type thrown ==> "); + assertMessageStartsWith(ex, "Unexpected exception type thrown, "); assertMessageContains(ex, "expected: "); // As of the time of this writing, the class name of the above anonymous inner // class is org.junit.jupiter.api.AssertionsAssertThrowsTests$2; however, hard // coding "$2" is fragile. So we just check for the presence of the "$" // appended to this class's name. assertMessageContains(ex, "but was: <" + getClass().getName() + "$"); + assertThat(ex).hasCauseInstanceOf(NumberFormatException.class); } } @Test void assertThrowsWithExecutableThatThrowsInstanceOfStaticNestedClassAsUnexpectedException() { try { - assertThrows(IllegalStateException.class, (Executable) () -> { + assertThrows(IllegalStateException.class, () -> { throw new LocalException(); }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "Unexpected exception type thrown ==> "); + assertMessageStartsWith(ex, "Unexpected exception type thrown, "); assertMessageContains(ex, "expected: "); // The following verifies that the canonical name is used (i.e., "." instead of "$"). assertMessageContains(ex, "but was: <" + LocalException.class.getName().replace("$", ".") + ">"); + assertThat(ex).hasCauseInstanceOf(LocalException.class); } } @@ -241,7 +248,7 @@ void assertThrowsWithExecutableThatThrowsSameExceptionTypeFromDifferentClassLoad EnigmaThrowable.class.getName()); try { - assertThrows(enigmaThrowableClass, (Executable) () -> { + assertThrows(enigmaThrowableClass, () -> { throw new EnigmaThrowable(); }); expectAssertionFailedError(); @@ -249,23 +256,25 @@ void assertThrowsWithExecutableThatThrowsSameExceptionTypeFromDifferentClassLoad catch (AssertionFailedError ex) { // Example Output: // - // Unexpected exception type thrown ==> + // Unexpected exception type thrown, // expected: // but was: - assertMessageStartsWith(ex, "Unexpected exception type thrown ==> "); + assertMessageStartsWith(ex, "Unexpected exception type thrown, "); // The presence of the "@" sign is sufficient to indicate that the hash was // generated to disambiguate between the two identical class names. assertMessageContains(ex, "expected: { @@ -39,7 +42,7 @@ class AssertThrowsExactlyAssertionsTests { @Test void assertThrowsExactlyTheSpecifiedExceptionClass() { - var actual = assertThrowsExactly(EnigmaThrowable.class, (Executable) () -> { + var actual = assertThrowsExactly(EnigmaThrowable.class, () -> { throw new EnigmaThrowable(); }); assertNotNull(actual); @@ -48,30 +51,32 @@ void assertThrowsExactlyTheSpecifiedExceptionClass() { @Test void assertThrowsExactlyWithTheExpectedChildException() { try { - assertThrowsExactly(RuntimeException.class, (Executable) () -> { + assertThrowsExactly(RuntimeException.class, () -> { throw new Exception(); }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "Unexpected exception type thrown ==> "); + assertMessageStartsWith(ex, "Unexpected exception type thrown, "); assertMessageContains(ex, "expected: "); assertMessageContains(ex, "but was: "); + assertThat(ex).hasCauseExactlyInstanceOf(Exception.class); } } @Test void assertThrowsExactlyWithTheExpectedParentException() { try { - assertThrowsExactly(RuntimeException.class, (Executable) () -> { + assertThrowsExactly(RuntimeException.class, () -> { throw new NumberFormatException(); }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "Unexpected exception type thrown ==> "); + assertMessageStartsWith(ex, "Unexpected exception type thrown, "); assertMessageContains(ex, "expected: "); assertMessageContains(ex, "but was: "); + assertThat(ex).hasCauseExactlyInstanceOf(NumberFormatException.class); } } @@ -101,7 +106,7 @@ void assertThrowsWithMethodReferenceForVoidReturnType() { @Test void assertThrowsWithExecutableThatThrowsThrowable() { - EnigmaThrowable enigmaThrowable = assertThrowsExactly(EnigmaThrowable.class, (Executable) () -> { + EnigmaThrowable enigmaThrowable = assertThrowsExactly(EnigmaThrowable.class, () -> { throw new EnigmaThrowable(); }); assertNotNull(enigmaThrowable); @@ -109,7 +114,7 @@ void assertThrowsWithExecutableThatThrowsThrowable() { @Test void assertThrowsWithExecutableThatThrowsThrowableWithMessage() { - EnigmaThrowable enigmaThrowable = assertThrowsExactly(EnigmaThrowable.class, (Executable) () -> { + EnigmaThrowable enigmaThrowable = assertThrowsExactly(EnigmaThrowable.class, () -> { throw new EnigmaThrowable(); }, "message"); assertNotNull(enigmaThrowable); @@ -117,7 +122,7 @@ void assertThrowsWithExecutableThatThrowsThrowableWithMessage() { @Test void assertThrowsWithExecutableThatThrowsThrowableWithMessageSupplier() { - EnigmaThrowable enigmaThrowable = assertThrowsExactly(EnigmaThrowable.class, (Executable) () -> { + EnigmaThrowable enigmaThrowable = assertThrowsExactly(EnigmaThrowable.class, () -> { throw new EnigmaThrowable(); }, () -> "message"); assertNotNull(enigmaThrowable); @@ -125,7 +130,7 @@ void assertThrowsWithExecutableThatThrowsThrowableWithMessageSupplier() { @Test void assertThrowsWithExecutableThatThrowsCheckedException() { - IOException exception = assertThrowsExactly(IOException.class, (Executable) () -> { + IOException exception = assertThrowsExactly(IOException.class, () -> { throw new IOException(); }); assertNotNull(exception); @@ -133,17 +138,16 @@ void assertThrowsWithExecutableThatThrowsCheckedException() { @Test void assertThrowsWithExecutableThatThrowsRuntimeException() { - IllegalStateException illegalStateException = assertThrowsExactly(IllegalStateException.class, - (Executable) () -> { - throw new IllegalStateException(); - }); + IllegalStateException illegalStateException = assertThrowsExactly(IllegalStateException.class, () -> { + throw new IllegalStateException(); + }); assertNotNull(illegalStateException); } @Test void assertThrowsWithExecutableThatThrowsError() { StackOverflowError stackOverflowError = assertThrowsExactly(StackOverflowError.class, - (Executable) AssertionTestUtils::recurseIndefinitely); + AssertionTestUtils::recurseIndefinitely); assertNotNull(stackOverflowError); } @@ -185,88 +189,92 @@ void assertThrowsWithExecutableThatDoesNotThrowAnExceptionWithMessageSupplier() @Test void assertThrowsWithExecutableThatThrowsAnUnexpectedException() { try { - assertThrowsExactly(IllegalStateException.class, (Executable) () -> { + assertThrowsExactly(IllegalStateException.class, () -> { throw new NumberFormatException(); }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "Unexpected exception type thrown ==> "); + assertMessageStartsWith(ex, "Unexpected exception type thrown, "); assertMessageContains(ex, "expected: "); assertMessageContains(ex, "but was: "); + assertThat(ex).hasCauseExactlyInstanceOf(NumberFormatException.class); } } @Test void assertThrowsWithExecutableThatThrowsAnUnexpectedExceptionWithMessageString() { try { - assertThrowsExactly(IllegalStateException.class, (Executable) () -> { + assertThrowsExactly(IllegalStateException.class, () -> { throw new NumberFormatException(); }, "Custom message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { // Should look something like this: - // Custom message ==> Unexpected exception type thrown ==> expected: but was: + // Custom message ==> Unexpected exception type thrown, expected: but was: assertMessageStartsWith(ex, "Custom message ==> "); - assertMessageContains(ex, "Unexpected exception type thrown ==> "); + assertMessageContains(ex, "Unexpected exception type thrown, "); assertMessageContains(ex, "expected: "); assertMessageContains(ex, "but was: "); + assertThat(ex).hasCauseExactlyInstanceOf(NumberFormatException.class); } } @Test void assertThrowsWithExecutableThatThrowsAnUnexpectedExceptionWithMessageSupplier() { try { - assertThrowsExactly(IllegalStateException.class, (Executable) () -> { + assertThrowsExactly(IllegalStateException.class, () -> { throw new NumberFormatException(); }, () -> "Custom message"); expectAssertionFailedError(); } catch (AssertionFailedError ex) { // Should look something like this: - // Custom message ==> Unexpected exception type thrown ==> expected: but was: + // Custom message ==> Unexpected exception type thrown, expected: but was: assertMessageStartsWith(ex, "Custom message ==> "); - assertMessageContains(ex, "Unexpected exception type thrown ==> "); + assertMessageContains(ex, "Unexpected exception type thrown, "); assertMessageContains(ex, "expected: "); assertMessageContains(ex, "but was: "); + assertThat(ex).hasCauseExactlyInstanceOf(NumberFormatException.class); } } @Test - @SuppressWarnings("serial") void assertThrowsWithExecutableThatThrowsInstanceOfAnonymousInnerClassAsUnexpectedException() { try { - assertThrowsExactly(IllegalStateException.class, (Executable) () -> { + assertThrowsExactly(IllegalStateException.class, () -> { throw new NumberFormatException() { }; }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "Unexpected exception type thrown ==> "); + assertMessageStartsWith(ex, "Unexpected exception type thrown, "); assertMessageContains(ex, "expected: "); // As of the time of this writing, the class name of the above anonymous inner // class is org.junit.jupiter.api.AssertThrowsExactlyAssertionsTests$2; however, hard // coding "$2" is fragile. So we just check for the presence of the "$" // appended to this class's name. assertMessageContains(ex, "but was: <" + getClass().getName() + "$"); + assertThat(ex).hasCauseInstanceOf(NumberFormatException.class); } } @Test void assertThrowsWithExecutableThatThrowsInstanceOfStaticNestedClassAsUnexpectedException() { try { - assertThrowsExactly(IllegalStateException.class, (Executable) () -> { + assertThrowsExactly(IllegalStateException.class, () -> { throw new LocalException(); }); expectAssertionFailedError(); } catch (AssertionFailedError ex) { - assertMessageStartsWith(ex, "Unexpected exception type thrown ==> "); + assertMessageStartsWith(ex, "Unexpected exception type thrown, "); assertMessageContains(ex, "expected: "); // The following verifies that the canonical name is used (i.e., "." instead of "$"). assertMessageContains(ex, "but was: <" + LocalException.class.getName().replace("$", ".") + ">"); + assertThat(ex).hasCauseExactlyInstanceOf(LocalException.class); } } @@ -280,7 +288,7 @@ void assertThrowsWithExecutableThatThrowsSameExceptionTypeFromDifferentClassLoad EnigmaThrowable.class.getName()); try { - assertThrowsExactly(enigmaThrowableClass, (Executable) () -> { + assertThrowsExactly(enigmaThrowableClass, () -> { throw new EnigmaThrowable(); }); expectAssertionFailedError(); @@ -288,23 +296,25 @@ void assertThrowsWithExecutableThatThrowsSameExceptionTypeFromDifferentClassLoad catch (AssertionFailedError ex) { // Example Output: // - // Unexpected exception type thrown ==> + // Unexpected exception type thrown, // expected: // but was: - assertMessageStartsWith(ex, "Unexpected exception type thrown ==> "); + assertMessageStartsWith(ex, "Unexpected exception type thrown, "); // The presence of the "@" sign is sufficient to indicate that the hash was // generated to disambiguate between the two identical class names. assertMessageContains(ex, "expected: