Document the effect of @DirtiesContext on test execution events

See gh-27757
This commit is contained in:
Sam Brannen 2022-03-12 16:02:20 +01:00
parent 8a510db00d
commit d9c22e657f
3 changed files with 245 additions and 1 deletions

View File

@ -63,7 +63,10 @@ import org.springframework.test.context.support.AbstractTestExecutionListener;
* 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.
*
* <h3>Exception Handling</h3>
* <p>By default, if a test event listener throws an exception while consuming

View File

@ -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<Class<? extends TestContextEvent>> 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());
}
}
}

View File

@ -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