@ -16,6 +16,10 @@
@@ -16,6 +16,10 @@
package org.springframework.test.context.cache ;
import java.util.ArrayList ;
import java.util.Collections ;
import java.util.List ;
import org.junit.jupiter.api.AfterEach ;
import org.junit.jupiter.api.BeforeEach ;
import org.junit.jupiter.api.ClassOrderer ;
@ -23,20 +27,26 @@ import org.junit.jupiter.api.Nested;
@@ -23,20 +27,26 @@ import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Order ;
import org.junit.jupiter.api.Test ;
import org.junit.jupiter.api.TestClassOrder ;
import org.junit.jupiter.api.TestInstance ;
import org.junit.jupiter.api.TestInstance.Lifecycle ;
import org.junit.platform.testkit.engine.EngineTestKit ;
import org.springframework.beans.factory.annotation.Value ;
import org.springframework.context.annotation.Configuration ;
import org.springframework.context.annotation.Import ;
import org.springframework.test.annotation.DirtiesContext ;
import org.springframework.test.context.ContextCustomizerFactories ;
import org.springframework.test.context.NestedTestConfiguration ;
import org.springframework.test.context.TestContext ;
import org.springframework.test.context.TestPropertySource ;
import org.springframework.test.context.cache.ContextCache.PauseMode ;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig ;
import static org.assertj.core.api.Assertions.assertThat ;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasses ;
import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT ;
import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE ;
import static org.springframework.test.context.cache.ContextCacheTestUtils.resetContextCache ;
/ * *
* Integration tests for pausing and restarting "unused" contexts .
@ -49,10 +59,63 @@ class UnusedContextsIntegrationTests {
@@ -49,10 +59,63 @@ class UnusedContextsIntegrationTests {
@BeforeEach
@AfterEach
void clearApplicationEvents ( ) {
void clearApplicationEventsAndResetContextCache ( ) {
resetContextCache ( ) ;
EventTracker . events . clear ( ) ;
}
@Test
void topLevelTestClassesWithDifferentApplicationContexts ( ) {
runTestClasses ( 6 ,
TestCaseConfig1A . class ,
TestCaseConfig1B . class ,
TestCaseConfig2 . class ,
TestCaseConfig3 . class ,
TestCaseConfig4 . class ,
TestCaseConfig5 . class ) ;
assertThat ( EventTracker . events ) . containsExactly (
// --- TestCaseConfig1A --------------------------------------------
"ContextRefreshed:TestCaseConfig1A" ,
// No BeforeTestClass, since EventPublishingTestExecutionListener
// only publishes events for a context that has already been loaded.
"AfterTestClass:TestCaseConfig1A" ,
// --- TestCaseConfig1B --------------------------------------------
// Here we expect a BeforeTestClass event, since TestCaseConfig1B
// uses the same context as TestCaseConfig1A.
"BeforeTestClass:TestCaseConfig1B" ,
"AfterTestClass:TestCaseConfig1B" ,
// --- TestCaseConfig2 ---------------------------------------------
"ContextPaused:TestCaseConfig1A" ,
"ContextRefreshed:TestCaseConfig2" ,
"AfterTestClass:TestCaseConfig2" ,
// --- TestCaseConfig3 ---------------------------------------------
"ContextPaused:TestCaseConfig2" ,
"ContextRefreshed:TestCaseConfig3" ,
"AfterTestClass:TestCaseConfig3" ,
// Closed instead of Paused, since TestCaseConfig3 uses @DirtiesContext
"ContextClosed:TestCaseConfig3" ,
// --- TestCaseConfig4 ---------------------------------------------
"ContextRefreshed:TestCaseConfig4" ,
"AfterTestClass:TestCaseConfig4" ,
// --- TestCaseConfig5 ---------------------------------------------
"ContextPaused:TestCaseConfig4" ,
"ContextRefreshed:TestCaseConfig5" ,
"AfterTestClass:TestCaseConfig5"
) ;
}
/ * *
* Since { @link PauseMode # ON_CONTEXT_SWITCH } is now the default , there are
* no { @code ContextPausedEvent } or { @code ContextRestartedEvent } events
* when all test classes share the same context .
* /
@Test
void topLevelTestClassesWithSharedApplicationContext ( ) {
runTestClasses ( 5 , TestCase1 . class , TestCase2 . class , TestCase3 . class , TestCase4 . class , TestCase5 . class ) ;
@ -60,58 +123,63 @@ class UnusedContextsIntegrationTests {
@@ -60,58 +123,63 @@ class UnusedContextsIntegrationTests {
assertThat ( EventTracker . events ) . containsExactly (
// --- TestCase1 -----------------------------------------------
// Refreshed instead of Restarted, since this is the first time
// the context is loaded.
// Refreshed, since this is the first time the context is loaded.
"ContextRefreshed:TestCase1" ,
// No BeforeTestClass, since EventPublishingTestExecutionListener
// only publishes events for a context that has already been loaded.
"AfterTestClass:TestCase1" ,
"ContextPaused:TestCase1" ,
// --- TestCase2 -----------------------------------------------
"ContextRestarted:TestCase1" ,
"BeforeTestClass:TestCase2" ,
"AfterTestClass:TestCase2" ,
"ContextPaused:TestCase1" ,
// --- TestCase3 -----------------------------------------------
"ContextRestarted:TestCase1" ,
"BeforeTestClass:TestCase3" ,
"AfterTestClass:TestCase3" ,
// Closed instead of Stopp ed, since TestCase3 uses @DirtiesContext
// Closed instead of Paus ed, since TestCase3 uses @DirtiesContext
"ContextClosed:TestCase1" ,
// --- TestCase4 -----------------------------------------------
// Refreshed instead of Restarted , since TestCase3 uses @DirtiesContext
// Refreshed, since TestCase3 uses @DirtiesContext
"ContextRefreshed:TestCase4" ,
// No BeforeTestClass, since EventPublishingTestExecutionListener
// only publishes events for a context that has already been loaded.
"AfterTestClass:TestCase4" ,
"ContextPaused:TestCase4" ,
// --- TestCase5 -----------------------------------------------
"ContextRestarted:TestCase4" ,
"BeforeTestClass:TestCase5" ,
"AfterTestClass:TestCase5" ,
"ContextPaused:TestCase4"
"AfterTestClass:TestCase5"
) ;
}
@Test
void testClassesInNestedTestHierarchy ( ) {
runTestClasses ( 5 , EnclosingTestCase . class ) ;
testClassesInNestedTestHierarchy ( EnclosingTestCase . class , false ) ;
}
assertThat ( EventTracker . events ) . containsExactly (
@Test
void testClassesInNestedTestHierarchyWithTestInstanceLifecyclePerClass ( ) {
testClassesInNestedTestHierarchy ( TestInstancePerClassEnclosingTestCase . class , true ) ;
}
private void testClassesInNestedTestHierarchy ( Class < ? > enclosingClass , boolean expectBeforeTestClassEvent ) {
// We also run a stand-alone top-level test class after the nested hierarchy,
// in order to verify what happens for a context switch from a nested hierarchy
// to something else.
runTestClasses ( 7 , enclosingClass , TestCase1 . class ) ;
String enclosingClassName = enclosingClass . getSimpleName ( ) ;
String [ ] events = {
// --- EnclosingTestCase -------------------------------------------
"ContextRefreshed:EnclosingTestCase" ,
"ContextRefreshed:" + enclosingClassName ,
// No BeforeTestClass, since EventPublishingTestExecutionListener
// only publishes events for a context that has already been loaded.
// --- NestedTestCase ------------------------------------------
// No Refreshed or Restarted event, since NestedTestCase shares the
// --- NestedTestCase1 -----------------------------------------
// No Refreshed or Restarted event, since NestedTestCase1 shares the
// active context used by EnclosingTestCase.
"BeforeTestClass:NestedTestCase" ,
"BeforeTestClass:NestedTestCase1 " ,
// --- OverridingNestedTestCase1 ---------------------------
"ContextRefreshed:OverridingNestedTestCase1" ,
@ -123,34 +191,64 @@ class UnusedContextsIntegrationTests {
@@ -123,34 +191,64 @@ class UnusedContextsIntegrationTests {
// shares the active context used by OverridingNestedTestCase1.
"BeforeTestClass:InheritingNestedTestCase" ,
"AfterTestClass:InheritingNestedTestCase" ,
// No Stopp ed event, since OverridingNestedTestCase1 is still
// No Paus ed event, since OverridingNestedTestCase1 is still
// using the context
"AfterTestClass:OverridingNestedTestCase1" ,
"ContextPaused:OverridingNestedTestCase1" ,
// No Paused event, since OverridingNestedTestCase2 will reuse
// the context
// --- OverridingNestedTestCase2 ---------------------------
"ContextRestarted:OverridingNestedTestCase1" ,
// No Restarted event, since OverridingNestedTestCase2 will reuse
// the context
"BeforeTestClass:OverridingNestedTestCase2" ,
"AfterTestClass:OverridingNestedTestCase2" ,
"ContextPaused:OverridingNestedTestCase1" ,
"AfterTestClass:NestedTestCase" ,
// No Stopp ed event, since EnclosingTestCase is still using the context
"AfterTestClass:NestedTestCase1 " ,
// No Paus ed event, since EnclosingTestCase is still using the context
"AfterTestClass:EnclosingTestCase" ,
"ContextPaused:EnclosingTestCase"
) ;
// --- NestedTestCase2 -----------------------------------------
// Refreshed, since this is the first time the context is loaded.
"ContextRefreshed:NestedTestCase2" ,
"AfterTestClass:NestedTestCase2" ,
// Paused, since the context used by NestedTestCase2 is no longer used,
// and EventPublishingTestExecutionListener.afterTestClass() "gets" the
// context for the enclosing class again, which constitutes a context switch.
"ContextPaused:NestedTestCase2" ,
"AfterTestClass:" + enclosingClassName ,
// --- TestCase1 ---------------------------------------------------
// Paused, since the context for the enclosing class is no longer used.
"ContextPaused:" + enclosingClassName ,
// Refreshed, since this is the first time the context is loaded.
"ContextRefreshed:TestCase1" ,
// No BeforeTestClass, since EventPublishingTestExecutionListener
// only publishes events for a context that has already been loaded.
"AfterTestClass:TestCase1" ,
} ;
List < String > eventsList = new ArrayList < > ( ) ;
Collections . addAll ( eventsList , events ) ;
if ( expectBeforeTestClassEvent ) {
eventsList . add ( 1 , "BeforeTestClass:" + enclosingClassName ) ;
}
assertThat ( EventTracker . events ) . containsExactlyElementsOf ( eventsList ) ;
}
@Test
void testClassesWithContextHierarchies ( ) {
runTestClasses ( 5 ,
// We also run a stand-alone top-level test class after the context hierarchy,
// in order to verify what happens for a context switch from a context hierarchy
// to something else.
runTestClasses ( 6 ,
ContextHierarchyLevel1TestCase . class ,
ContextHierarchyLevel2TestCase . class ,
ContextHierarchyLevel3a1TestCase . class ,
ContextHierarchyLevel3a2TestCase . class ,
ContextHierarchyLevel3bTestCase . class
ContextHierarchyLevel3bTestCase . class ,
TestCase1 . class
) ;
assertThat ( EventTracker . events ) . containsExactly (
@ -158,42 +256,48 @@ class UnusedContextsIntegrationTests {
@@ -158,42 +256,48 @@ class UnusedContextsIntegrationTests {
// --- ContextHierarchyLevel1TestCase ------------------------------
"ContextRefreshed:ContextHierarchyLevel1TestCase" ,
"AfterTestClass:ContextHierarchyLevel1TestCase" ,
"ContextPaused:ContextHierarchyLevel1TestCase" ,
// No Paused event, since ContextHierarchyLevel2TestCase uses
// ContextHierarchyLevel1TestCase as its parent.
// --- ContextHierarchyLevel2TestCase ------------------------------
"ContextRestarted:ContextHierarchyLevel1TestCase" ,
"ContextRefreshed:ContextHierarchyLevel2TestCase" ,
"AfterTestClass:ContextHierarchyLevel2TestCase" ,
"ContextPaused:ContextHierarchyLevel2TestCase" ,
"ContextPaused:ContextHierarchyLevel1TestCase" ,
// No Paused events, since ContextHierarchyLevel3a1TestCase uses
// ContextHierarchyLevel2TestCase and ContextHierarchyLevel1TestCase
// as its parents.
// --- ContextHierarchyLevel3a1TestCase -----------------------------
"ContextRestarted:ContextHierarchyLevel1TestCase" ,
"ContextRestarted:ContextHierarchyLevel2TestCase" ,
"ContextRefreshed:ContextHierarchyLevel3a1TestCase" ,
"AfterTestClass:ContextHierarchyLevel3a1TestCase" ,
"ContextPaused:ContextHierarchyLevel3a1TestCase" ,
"ContextPaused:ContextHierarchyLevel2TestCase" ,
"ContextPaused:ContextHierarchyLevel1TestCase" ,
// No Paused events, since ContextHierarchyLevel3a2TestCase also uses
// ContextHierarchyLevel2TestCase and ContextHierarchyLevel1TestCase
// as its parents.
// --- ContextHierarchyLevel3a2TestCase -----------------------------
"ContextRestarted:ContextHierarchyLevel1TestCase" ,
"ContextRestarted:ContextHierarchyLevel2TestCase" ,
"ContextRestarted:ContextHierarchyLevel3a1TestCase" ,
"BeforeTestClass:ContextHierarchyLevel3a2TestCase" ,
"AfterTestClass:ContextHierarchyLevel3a2TestCase" ,
"ContextPaused:ContextHierarchyLevel3a1TestCase" ,
"ContextPaused:ContextHierarchyLevel2TestCase" ,
"ContextPaused:ContextHierarchyLevel1TestCase" ,
// --- ContextHierarchyLevel3bTestCase -----------------------------
"ContextRestarted:ContextHierarchyLevel1TestCase" ,
"ContextRestarted:ContextHierarchyLevel2TestCase" ,
// We see a ContextPausedEvent here, since ContextHierarchyLevel3a1TestCase
// and ContextHierarchyLevel3a2TestCase are no longer active and we
// are "switching" to ContextHierarchyLevel3bTestCase as the active context.
// In other words, we pause the inactive context before refreshing the
// new, active context.
"ContextPaused:ContextHierarchyLevel3a1TestCase" ,
"ContextRefreshed:ContextHierarchyLevel3bTestCase" ,
"AfterTestClass:ContextHierarchyLevel3bTestCase" ,
// --- TestCase1 ---------------------------------------------------
// Paused, since the previous context hierarchy is no longer used.
// Note that the pause order is bottom up.
"ContextPaused:ContextHierarchyLevel3bTestCase" ,
"ContextPaused:ContextHierarchyLevel2TestCase" ,
"ContextPaused:ContextHierarchyLevel1TestCase"
"ContextPaused:ContextHierarchyLevel1TestCase" ,
// Refreshed, since this is the first time the context is loaded.
"ContextRefreshed:TestCase1" ,
// No BeforeTestClass, since EventPublishingTestExecutionListener
// only publishes events for a context that has already been loaded.
"AfterTestClass:TestCase1"
) ;
}
@ -233,7 +337,58 @@ class UnusedContextsIntegrationTests {
@@ -233,7 +337,58 @@ class UnusedContextsIntegrationTests {
static class TestCase5 extends AbstractTestCase {
}
@Configuration
@Import ( EventTracker . class )
static class Config1 {
}
@Configuration
@Import ( EventTracker . class )
static class Config2 {
}
@Configuration
@Import ( EventTracker . class )
static class Config3 {
}
@Configuration
@Import ( EventTracker . class )
static class Config4 {
}
@Configuration
@Import ( EventTracker . class )
static class Config5 {
}
@SpringJUnitConfig ( Config1 . class )
static class TestCaseConfig1A extends AbstractTestCase {
}
@SpringJUnitConfig ( Config1 . class )
static class TestCaseConfig1B extends AbstractTestCase {
}
@SpringJUnitConfig ( Config2 . class )
static class TestCaseConfig2 extends AbstractTestCase {
}
@SpringJUnitConfig ( Config3 . class )
@DirtiesContext
static class TestCaseConfig3 extends AbstractTestCase {
}
@SpringJUnitConfig ( Config4 . class )
static class TestCaseConfig4 extends AbstractTestCase {
}
@SpringJUnitConfig ( Config5 . class )
static class TestCaseConfig5 extends AbstractTestCase {
}
@SpringJUnitConfig ( EventTracker . class )
@TestClassOrder ( ClassOrderer . OrderAnnotation . class )
@ContextCustomizerFactories ( DisplayNameCustomizerFactory . class )
@TestPropertySource ( properties = "magicKey = puzzle" )
static class EnclosingTestCase {
@ -244,8 +399,8 @@ class UnusedContextsIntegrationTests {
@@ -244,8 +399,8 @@ class UnusedContextsIntegrationTests {
}
@Nested
@TestClass Order ( ClassOrderer . OrderAnnotation . class )
class NestedTestCase {
@Order ( 1 )
class NestedTestCase1 {
@Test
void test ( @Value ( "${magicKey}" ) String magicKey ) {
@ -296,6 +451,24 @@ class UnusedContextsIntegrationTests {
@@ -296,6 +451,24 @@ class UnusedContextsIntegrationTests {
}
}
}
@Nested
@Order ( 2 )
@NestedTestConfiguration ( OVERRIDE )
@SpringJUnitConfig ( EventTracker . class )
@ContextCustomizerFactories ( DisplayNameCustomizerFactory . class )
@TestPropertySource ( properties = "magicKey = enigma2" )
class NestedTestCase2 {
@Test
void test ( @Value ( "${magicKey}" ) String magicKey ) {
assertThat ( magicKey ) . isEqualTo ( "enigma2" ) ;
}
}
}
@TestInstance ( Lifecycle . PER_CLASS )
static class TestInstancePerClassEnclosingTestCase extends EnclosingTestCase {
}
}