blob: 107e3e9c8fb63236f3dec37107cb13fc8ff71145 [file]
/*
* 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.engine;
import static org.junit.jupiter.api.Assumptions.assumeFalse;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod;
import static org.junit.platform.testkit.engine.EventConditions.container;
import static org.junit.platform.testkit.engine.EventConditions.engine;
import static org.junit.platform.testkit.engine.EventConditions.event;
import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully;
import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure;
import static org.junit.platform.testkit.engine.EventConditions.started;
import static org.junit.platform.testkit.engine.EventConditions.test;
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf;
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message;
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.suppressed;
import java.io.IOException;
import java.util.Optional;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.platform.testkit.engine.EngineExecutionResults;
import org.junit.platform.testkit.engine.Events;
import org.opentest4j.AssertionFailedError;
import org.opentest4j.TestAbortedException;
/**
* Integration tests that verify correct exception handling in the {@link JupiterTestEngine}.
*
* @since 5.0
*/
class ExceptionHandlingTests extends AbstractJupiterTestEngineTests {
@Test
void failureInTestMethodIsRegistered() {
EngineExecutionResults executionResults = executeTests(selectMethod(FailureTestCase.class, "failingTest"));
Events tests = executionResults.testEvents();
tests.assertStatistics(stats -> stats.started(1).failed(1));
tests.failed().assertEventsMatchExactly( //
event(test("failingTest"),
finishedWithFailure(instanceOf(AssertionFailedError.class), message("always fails"))));
}
@Test
void uncheckedExceptionInTestMethodIsRegistered() {
EngineExecutionResults executionResults = executeTests(
selectMethod(FailureTestCase.class, "testWithUncheckedException"));
Events tests = executionResults.testEvents();
tests.assertStatistics(stats -> stats.started(1).failed(1));
tests.failed().assertEventsMatchExactly( //
event(test("testWithUncheckedException"),
finishedWithFailure(instanceOf(RuntimeException.class), message("unchecked"))));
}
@Test
void checkedExceptionInTestMethodIsRegistered() {
EngineExecutionResults executionResults = executeTests(
selectMethod(FailureTestCase.class, "testWithCheckedException"));
Events tests = executionResults.testEvents();
tests.assertStatistics(stats -> stats.started(1).failed(1));
tests.failed().assertEventsMatchExactly( //
event(test("testWithCheckedException"),
finishedWithFailure(instanceOf(IOException.class), message("checked"))));
}
@Test
void checkedExceptionInBeforeEachIsRegistered() {
FailureTestCase.exceptionToThrowInBeforeEach = Optional.of(new IOException("checked"));
EngineExecutionResults executionResults = executeTests(selectMethod(FailureTestCase.class, "succeedingTest"));
Events tests = executionResults.testEvents();
tests.assertStatistics(stats -> stats.started(1).failed(1));
tests.failed().assertEventsMatchExactly(
event(test("succeedingTest"), finishedWithFailure(instanceOf(IOException.class), message("checked"))));
}
@Test
void checkedExceptionInAfterEachIsRegistered() {
FailureTestCase.exceptionToThrowInAfterEach = Optional.of(new IOException("checked"));
EngineExecutionResults executionResults = executeTests(selectMethod(FailureTestCase.class, "succeedingTest"));
Events tests = executionResults.testEvents();
tests.assertStatistics(stats -> stats.started(1).failed(1));
tests.failed().assertEventsMatchExactly(
event(test("succeedingTest"), finishedWithFailure(instanceOf(IOException.class), message("checked"))));
}
@Test
void checkedExceptionInAfterEachIsSuppressedByExceptionInTest() {
Class<?> testClass = FailureTestCase.class;
FailureTestCase.exceptionToThrowInAfterEach = Optional.of(new IOException("checked"));
EngineExecutionResults executionResults = executeTests(selectMethod(testClass, "testWithUncheckedException"));
executionResults.allEvents().assertEventsMatchExactly( //
event(engine(), started()), //
event(container(testClass), started()), //
event(test("testWithUncheckedException"), started()), //
event(test("testWithUncheckedException"), //
finishedWithFailure( //
instanceOf(RuntimeException.class), //
message("unchecked"), //
suppressed(0, instanceOf(IOException.class), message("checked")))), //
event(container(testClass), finishedSuccessfully()), //
event(engine(), finishedSuccessfully()));
}
@Test
void exceptionInAfterEachTakesPrecedenceOverFailedAssumptionInTest() {
FailureTestCase.exceptionToThrowInAfterEach = Optional.of(new IOException("checked"));
EngineExecutionResults executionResults = executeTests(selectMethod(FailureTestCase.class, "abortedTest"));
executionResults.allEvents().assertEventsMatchExactly( //
event(engine(), started()), //
event(container(FailureTestCase.class), started()), //
event(test("abortedTest"), started()), //
event(test("abortedTest"), //
finishedWithFailure(instanceOf(IOException.class), message("checked"), //
suppressed(0, instanceOf(TestAbortedException.class)))), //
event(container(FailureTestCase.class), finishedSuccessfully()), //
event(engine(), finishedSuccessfully()));
}
@Test
void checkedExceptionInBeforeAllIsRegistered() {
Class<?> testClass = FailureTestCase.class;
FailureTestCase.exceptionToThrowInBeforeAll = Optional.of(new IOException("checked"));
EngineExecutionResults executionResults = executeTests(selectMethod(testClass, "succeedingTest"));
executionResults.allEvents().assertEventsMatchExactly( //
event(engine(), started()), //
event(container(testClass), started()), //
event(container(testClass), finishedWithFailure(instanceOf(IOException.class), message("checked"))), //
event(engine(), finishedSuccessfully()));
}
@Test
void checkedExceptionInAfterAllIsRegistered() {
Class<?> testClass = FailureTestCase.class;
FailureTestCase.exceptionToThrowInAfterAll = Optional.of(new IOException("checked"));
EngineExecutionResults executionResults = executeTests(selectMethod(testClass, "succeedingTest"));
executionResults.allEvents().assertEventsMatchExactly( //
event(engine(), started()), //
event(container(testClass), started()), //
event(test("succeedingTest"), started()), //
event(test("succeedingTest"), finishedSuccessfully()), //
event(container(testClass), finishedWithFailure(instanceOf(IOException.class), message("checked"))), //
event(engine(), finishedSuccessfully()));
}
@Test
void exceptionInAfterAllCallbackDoesNotHideExceptionInBeforeAllCallback() {
Class<?> testClass = TestCaseWithThrowingBeforeAllAndAfterAllCallbacks.class;
EngineExecutionResults executionResults = executeTestsForClass(testClass);
executionResults.allEvents().assertEventsMatchExactly( //
event(engine(), started()), //
event(container(testClass), started()), //
event(container(testClass), finishedWithFailure( //
message("beforeAll callback"), //
suppressed(0, message("afterAll callback")))), //
event(engine(), finishedSuccessfully()));
}
@Test
void exceptionsInConstructorAndAfterAllCallbackAreReportedWhenTestInstancePerMethodIsUsed() {
Class<?> testClass = TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerMethodLifecycle.class;
EngineExecutionResults executionResults = executeTestsForClass(testClass);
executionResults.allEvents().assertEventsMatchExactly( //
event(engine(), started()), //
event(container(testClass), started()), //
event(test("test"), started()), //
event(test("test"), finishedWithFailure(message("constructor"))), //
event(container(testClass), finishedWithFailure(message("afterAll callback"))), //
event(engine(), finishedSuccessfully()));
}
@Test
void exceptionInConstructorPreventsExecutionOfAfterAllCallbacksWhenTestInstancePerClassIsUsed() {
Class<?> testClass = TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerClassLifecycle.class;
EngineExecutionResults executionResults = executeTestsForClass(testClass);
executionResults.allEvents().assertEventsMatchExactly( //
event(engine(), started()), //
event(container(testClass), started()), //
event(container(testClass), finishedWithFailure(message("constructor"))),
event(engine(), finishedSuccessfully()));
}
@Test
void failureInAfterAllTakesPrecedenceOverTestAbortedExceptionInBeforeAll() {
FailureTestCase.exceptionToThrowInBeforeAll = Optional.of(new TestAbortedException("aborted"));
FailureTestCase.exceptionToThrowInAfterAll = Optional.of(new IOException("checked"));
EngineExecutionResults executionResults = executeTests(selectMethod(FailureTestCase.class, "succeedingTest"));
executionResults.allEvents().assertEventsMatchExactly( //
event(engine(), started()), //
event(container(FailureTestCase.class), started()), //
event(container(FailureTestCase.class),
finishedWithFailure(instanceOf(IOException.class), message("checked"),
suppressed(0, instanceOf(TestAbortedException.class), message("aborted")))), //
event(engine(), finishedSuccessfully()));
}
@AfterEach
void cleanUpExceptions() {
FailureTestCase.exceptionToThrowInBeforeAll = Optional.empty();
FailureTestCase.exceptionToThrowInAfterAll = Optional.empty();
FailureTestCase.exceptionToThrowInBeforeEach = Optional.empty();
FailureTestCase.exceptionToThrowInAfterEach = Optional.empty();
}
// -------------------------------------------------------------------------
static class FailureTestCase {
static Optional<Throwable> exceptionToThrowInBeforeAll = Optional.empty();
static Optional<Throwable> exceptionToThrowInAfterAll = Optional.empty();
static Optional<Throwable> exceptionToThrowInBeforeEach = Optional.empty();
static Optional<Throwable> exceptionToThrowInAfterEach = Optional.empty();
@BeforeAll
static void beforeAll() throws Throwable {
if (exceptionToThrowInBeforeAll.isPresent()) {
throw exceptionToThrowInBeforeAll.get();
}
}
@AfterAll
static void afterAll() throws Throwable {
if (exceptionToThrowInAfterAll.isPresent()) {
throw exceptionToThrowInAfterAll.get();
}
}
@BeforeEach
void beforeEach() throws Throwable {
if (exceptionToThrowInBeforeEach.isPresent()) {
throw exceptionToThrowInBeforeEach.get();
}
}
@AfterEach
void afterEach() throws Throwable {
if (exceptionToThrowInAfterEach.isPresent()) {
throw exceptionToThrowInAfterEach.get();
}
}
@Test
void succeedingTest() {
}
@Test
void failingTest() {
Assertions.fail("always fails");
}
@Test
void testWithUncheckedException() {
throw new RuntimeException("unchecked");
}
@Test
void testWithCheckedException() throws IOException {
throw new IOException("checked");
}
@Test
void abortedTest() {
assumeFalse(true, "abortedTest");
}
}
@TestInstance(PER_METHOD)
@ExtendWith(ThrowingAfterAllCallback.class)
static class TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerMethodLifecycle {
TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerMethodLifecycle() {
throw new IllegalStateException("constructor");
}
@Test
void test() {
}
}
@TestInstance(PER_CLASS)
@ExtendWith(ThrowingAfterAllCallback.class)
static class TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerClassLifecycle {
TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerClassLifecycle() {
throw new IllegalStateException("constructor");
}
@Test
void test() {
}
}
@ExtendWith(ThrowingBeforeAllCallback.class)
@ExtendWith(ThrowingAfterAllCallback.class)
static class TestCaseWithThrowingBeforeAllAndAfterAllCallbacks {
@Test
void test() {
}
}
static class ThrowingBeforeAllCallback implements BeforeAllCallback {
@Override
public void beforeAll(ExtensionContext context) {
throw new IllegalStateException("beforeAll callback");
}
}
static class ThrowingAfterAllCallback implements AfterAllCallback {
@Override
public void afterAll(ExtensionContext context) {
throw new IllegalStateException("afterAll callback");
}
}
}