From d9c22e657fe86e999cbb4de1d1e0096435a828bf Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 12 Mar 2022 16:02:20 +0100 Subject: [PATCH] Document the effect of @DirtiesContext on test execution events See gh-27757 --- .../EventPublishingTestExecutionListener.java | 5 +- .../DirtiesContextEventPublishingTests.java | 237 ++++++++++++++++++ src/docs/asciidoc/testing.adoc | 4 + 3 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 spring-test/src/test/java/org/springframework/test/context/event/DirtiesContextEventPublishingTests.java diff --git a/spring-test/src/main/java/org/springframework/test/context/event/EventPublishingTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/event/EventPublishingTestExecutionListener.java index d6b5c9cef4f3..953b5710ce89 100644 --- a/spring-test/src/main/java/org/springframework/test/context/event/EventPublishingTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/event/EventPublishingTestExecutionListener.java @@ -63,7 +63,10 @@ * register a {@code TestExecutionListener} that loads the {@code ApplicationContext} * in the {@link org.springframework.test.context.TestExecutionListener#beforeTestClass * beforeTestClass} callback, and that {@code TestExecutionListener} must be registered - * before the {@code EventPublishingTestExecutionListener}. + * before the {@code EventPublishingTestExecutionListener}. Similarly, if + * {@code @DirtiesContext} is used to remove the {@code ApplicationContext} from + * the context cache after the last test method in a given test class, the + * {@code AfterTestClassEvent} will not be published for that test class. * *

Exception Handling

*

By default, if a test event listener throws an exception while consuming diff --git a/spring-test/src/test/java/org/springframework/test/context/event/DirtiesContextEventPublishingTests.java b/spring-test/src/test/java/org/springframework/test/context/event/DirtiesContextEventPublishingTests.java new file mode 100644 index 000000000000..e6dab3c4f75c --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/event/DirtiesContextEventPublishingTests.java @@ -0,0 +1,237 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * 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 + * + * https://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 org.springframework.test.context.event; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.platform.testkit.engine.EngineTestKit; + +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.MethodMode; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.event.annotation.AfterTestClass; +import org.springframework.test.context.event.annotation.AfterTestExecution; +import org.springframework.test.context.event.annotation.AfterTestMethod; +import org.springframework.test.context.event.annotation.BeforeTestClass; +import org.springframework.test.context.event.annotation.BeforeTestExecution; +import org.springframework.test.context.event.annotation.BeforeTestMethod; +import org.springframework.test.context.event.annotation.PrepareTestInstance; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; + +/** + * Tests for the {@link EventPublishingTestExecutionListener} which verify + * behavior for test context events when {@link DirtiesContext @DirtiesContext} + * is used. + * + * @author Sam Brannen + * @since 5.3.17 + * @see https://github.com/spring-projects/spring-framework/issues/27757 + */ +class DirtiesContextEventPublishingTests { + + private static final List> events = new ArrayList<>(); + + + @BeforeEach + @AfterEach + void resetEvents() { + events.clear(); + } + + @Test + void classLevelDirtiesContext() { + EngineTestKit.engine("junit-jupiter")// + .selectors(selectClass(ClassLevelDirtiesContextTestCase.class))// + .execute()// + .testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1).failed(0)); + + assertThat(events).containsExactly(// + // BeforeTestClassEvent.class -- always missing for 1st test class by default + PrepareTestInstanceEvent.class, // + BeforeTestMethodEvent.class, // + BeforeTestExecutionEvent.class, // + AfterTestExecutionEvent.class, // + AfterTestMethodEvent.class, // + AfterTestClassEvent.class // + ); + } + + @Test + void methodLevelAfterMethodDirtiesContext() { + EngineTestKit.engine("junit-jupiter")// + .selectors(selectClass(MethodLevelAfterMethodDirtiesContextTestCase.class))// + .execute()// + .testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1).failed(0)); + + assertThat(events).containsExactly(// + // BeforeTestClassEvent.class -- always missing for 1st test class by default + PrepareTestInstanceEvent.class, // + BeforeTestMethodEvent.class, // + BeforeTestExecutionEvent.class, // + AfterTestExecutionEvent.class, // + AfterTestMethodEvent.class // + // AfterTestClassEvent.class -- missing b/c of @DirtiestContext "after method" at the method level + ); + } + + @Test + void methodLevelAfterMethodDirtiesContextWithSubsequentTestMethod() { + EngineTestKit.engine("junit-jupiter")// + .selectors(selectClass(MethodLevelAfterMethodDirtiesContextWithSubsequentTestMethodTestCase.class))// + .execute()// + .testEvents()// + .assertStatistics(stats -> stats.started(2).succeeded(2).failed(0)); + + assertThat(events).containsExactly(// + // BeforeTestClassEvent.class -- always missing for 1st test class by default + // test1() + PrepareTestInstanceEvent.class, // + BeforeTestMethodEvent.class, // + BeforeTestExecutionEvent.class, // + AfterTestExecutionEvent.class, // + AfterTestMethodEvent.class, // + // test2() + PrepareTestInstanceEvent.class, // + BeforeTestMethodEvent.class, // + BeforeTestExecutionEvent.class, // + AfterTestExecutionEvent.class, // + AfterTestMethodEvent.class, // + AfterTestClassEvent.class // b/c @DirtiestContext is not applied for test2() + ); + } + + @Test + void methodLevelBeforeMethodDirtiesContext() { + EngineTestKit.engine("junit-jupiter")// + .selectors(selectClass(MethodLevelBeforeMethodDirtiesContextTestCase.class))// + .execute()// + .testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1).failed(0)); + + assertThat(events).containsExactly(// + // BeforeTestClassEvent.class -- always missing for 1st test class by default + PrepareTestInstanceEvent.class, // + BeforeTestMethodEvent.class, // + BeforeTestExecutionEvent.class, // + AfterTestExecutionEvent.class, // + AfterTestMethodEvent.class, // + AfterTestClassEvent.class // b/c @DirtiestContext happens "before method" at the method level + ); + } + + @SpringJUnitConfig(Config.class) + // add unique property to get a unique ApplicationContext + @TestPropertySource(properties = "DirtiesContextEventPublishingTests.key = class-level") + @DirtiesContext + static class ClassLevelDirtiesContextTestCase { + + @Test + void test() { + } + } + + @SpringJUnitConfig(Config.class) + // add unique property to get a unique ApplicationContext + @TestPropertySource(properties = "DirtiesContextEventPublishingTests.key = method-level-after-method") + static class MethodLevelAfterMethodDirtiesContextTestCase { + + @Test + @DirtiesContext + void test1() { + } + } + + @SpringJUnitConfig(Config.class) + // add unique property to get a unique ApplicationContext + @TestPropertySource(properties = "DirtiesContextEventPublishingTests.key = method-level-after-method-with-subsequent-test-method") + @TestMethodOrder(DisplayName.class) + static class MethodLevelAfterMethodDirtiesContextWithSubsequentTestMethodTestCase { + + @Test + @DirtiesContext + void test1() { + } + + @Test + void test2() { + } + } + + @SpringJUnitConfig(Config.class) + // add unique property to get a unique ApplicationContext + @TestPropertySource(properties = "DirtiesContextEventPublishingTests.key = method-level-before-method") + static class MethodLevelBeforeMethodDirtiesContextTestCase { + + @Test + @DirtiesContext(methodMode = MethodMode.BEFORE_METHOD) + void test() { + } + } + + @Configuration + static class Config { + + @BeforeTestClass + public void beforeTestClass(BeforeTestClassEvent e) { + events.add(e.getClass()); + } + + @PrepareTestInstance + public void prepareTestInstance(PrepareTestInstanceEvent e) { + events.add(e.getClass()); + } + + @BeforeTestMethod + public void beforeTestMethod(BeforeTestMethodEvent e) { + events.add(e.getClass()); + } + + @BeforeTestExecution + public void beforeTestExecution(BeforeTestExecutionEvent e) { + events.add(e.getClass()); + } + + @AfterTestExecution + public void afterTestExecution(AfterTestExecutionEvent e) { + events.add(e.getClass()); + } + + @AfterTestMethod + public void afterTestMethod(AfterTestMethodEvent e) { + events.add(e.getClass()); + } + + @AfterTestClass + public void afterTestClass(AfterTestClassEvent e) { + events.add(e.getClass()); + } + + } + +} diff --git a/src/docs/asciidoc/testing.adoc b/src/docs/asciidoc/testing.adoc index 9a3dbf598e98..61727e9b0b98 100644 --- a/src/docs/asciidoc/testing.adoc +++ b/src/docs/asciidoc/testing.adoc @@ -2725,6 +2725,10 @@ If you wish to ensure that a `BeforeTestClassEvent` is always published for ever class, you need to register a `TestExecutionListener` that loads the `ApplicationContext` in the `beforeTestClass` callback, and that `TestExecutionListener` must be registered _before_ the `EventPublishingTestExecutionListener`. + +Similarly, if `@DirtiesContext` is used to remove the `ApplicationContext` from the +context cache after the last test method in a given test class, the `AfterTestClassEvent` +will not be published for that test class. ==== In order to listen to test execution events, a Spring bean may choose to implement the