diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractTestWithCallbacksExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractTestWithCallbacksExtension.java index 2717fe0fa4a40..c48fdc5624a9e 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractTestWithCallbacksExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractTestWithCallbacksExtension.java @@ -158,7 +158,12 @@ private void invokeCallbacks(List callbacks, String methodName, Class .invoke(callback, classInstance); } } catch (InvocationTargetException e) { - throw e.getCause() instanceof Exception ? (Exception) e.getCause() : e; + if (e.getCause() instanceof Exception) { + throw (Exception) e.getCause(); + } else if (e.getCause() instanceof AssertionError) { + throw (AssertionError) e.getCause(); + } + throw e; } } } diff --git a/test-framework/junit5/src/test/java/io/quarkus/test/junit/ErrorThrowingCallback.java b/test-framework/junit5/src/test/java/io/quarkus/test/junit/ErrorThrowingCallback.java new file mode 100644 index 0000000000000..a87f495c8ad8d --- /dev/null +++ b/test-framework/junit5/src/test/java/io/quarkus/test/junit/ErrorThrowingCallback.java @@ -0,0 +1,34 @@ +package io.quarkus.test.junit; + +import java.io.IOException; +import java.io.UncheckedIOException; + +import org.junit.jupiter.api.AssertionFailureBuilder; + +import io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback; +import io.quarkus.test.junit.callback.QuarkusTestMethodContext; + +/** + * Test handling of {@link AssertionError}s and {@link RuntimeException} in callbacks. + * + * @see QuarkusTestCallbacksErrorHandlingTest + */ +public class ErrorThrowingCallback implements QuarkusTestAfterEachCallback { + @Override + public void afterEach(QuarkusTestMethodContext context) { + String throwableType = System.getProperty("quarkus.test.callback.throwableType"); + + if ("AssertionError".equalsIgnoreCase(throwableType)) { + AssertionFailureBuilder + .assertionFailure() + .expected("a") + .actual("b") + .reason("Oh no, it broke! Here's an assertion error") + .buildAndThrow(); + } + + if ("RuntimeException".equalsIgnoreCase(throwableType)) { + throw new UncheckedIOException(new IOException("Oh dear, it broke again")); + } + } +} diff --git a/test-framework/junit5/src/test/java/io/quarkus/test/junit/QuarkusTestCallbacksErrorHandlingTest.java b/test-framework/junit5/src/test/java/io/quarkus/test/junit/QuarkusTestCallbacksErrorHandlingTest.java new file mode 100644 index 0000000000000..c56ff9877b43f --- /dev/null +++ b/test-framework/junit5/src/test/java/io/quarkus/test/junit/QuarkusTestCallbacksErrorHandlingTest.java @@ -0,0 +1,50 @@ +package io.quarkus.test.junit; + +import java.io.UncheckedIOException; +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.callback.QuarkusTestMethodContext; + +/** + * Generally, {@link AssertionError} is thrown by testing frameworks such as JUnit. However, + * it does not derive from {@link RuntimeException} hierarchy, and hence separate unwrapping of each type + * must be handled in {@link io.quarkus.test.junit.AbstractTestWithCallbacksExtension}. + *

+ * Ensuring that AssertionError is at the top of any stack track allows tooling to properly + * parse and display expected vs actual diffs, etc. + * + * @see ErrorThrowingCallback + */ +public class QuarkusTestCallbacksErrorHandlingTest { + + @Test + public void testAssertionErrorsAreUnwrappedFromCallback() { + Assertions.assertThrows(AssertionError.class, () -> { + System.setProperty("quarkus.test.callback.throwableType", "AssertionError"); + MockCallbackExtension extension = new MockCallbackExtension(); + QuarkusTestMethodContext mockContext = new QuarkusTestMethodContext(new Object(), List.of(), null, null); + extension.invokeAfterEachCallbacks(mockContext); + }); + } + + @Test + public void testRuntimeExceptionsAreUnwrappedFromCallback() { + Assertions.assertThrows(UncheckedIOException.class, () -> { + System.setProperty("quarkus.test.callback.throwableType", "RuntimeException"); + MockCallbackExtension extension = new MockCallbackExtension(); + QuarkusTestMethodContext mockContext = new QuarkusTestMethodContext(new Object(), List.of(), null, null); + extension.invokeAfterEachCallbacks(mockContext); + }); + } + + public static class MockCallbackExtension extends AbstractTestWithCallbacksExtension { + + public MockCallbackExtension() throws ClassNotFoundException { + populateCallbacks(Thread.currentThread().getContextClassLoader()); + } + + } +} diff --git a/test-framework/junit5/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback b/test-framework/junit5/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback new file mode 100644 index 0000000000000..833d325d836f8 --- /dev/null +++ b/test-framework/junit5/src/test/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback @@ -0,0 +1 @@ +io.quarkus.test.junit.ErrorThrowingCallback