Support global ExtensionContext scope configuration for SpringExtension

Prior to this commit, the extension context scope used by the
SpringExtension defaulted to test-method scoped and could only be
changed on a per-class basis via the @⁠SpringExtensionConfig
annotation. However, having to annotate each affected test class is
cumbersome for developers who wish to use test-class scoped extension
context semantics across their code base.

To address that, this commit introduces support for configuring the
global default extension context scope for the SpringExtension via
Spring properties or JUnit properties.

Specifically, this commit introduces a
`spring.test.extension.context.scope` property that can be set via the
SpringProperties mechanism or via a JUnit Platform configuration
parameter.

See gh-35697
Closes gh-36460
This commit is contained in:
Sam Brannen
2026-03-22 16:48:41 +01:00
parent 3a35c4dbd0
commit 43b01382ed
7 changed files with 401 additions and 58 deletions
@@ -138,6 +138,11 @@ xref:testing/testcontext-framework/ctx-management/context-pausing.adoc[Context P
in the _Spring TestContext Framework_. See
xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[Context Failure Threshold].
| `spring.test.extension.context.scope`
| The default _extension context scope_ used by the `SpringExtension` in `@Nested` test
class hierarchies. See
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-springextensionconfig[`@SpringExtensionConfig`].
| `spring.test.enclosing.configuration`
| The default _enclosing configuration inheritance mode_ to use if
`@NestedTestConfiguration` is not present on a test class. See
@@ -3,7 +3,7 @@
The following annotations are supported when used in conjunction with the
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[`SpringExtension`]
and JUnit Jupiter (that is, the programming model in JUnit):
and the JUnit Jupiter testing framework:
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-springextensionconfig[`@SpringExtensionConfig`]
* xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-junit-jupiter-springjunitconfig[`@SpringJUnitConfig`]
@@ -30,30 +30,40 @@ developer wishes to switch to test-class scoped semantics — the `SpringExtensi
configured to use a test-class scoped `ExtensionContext` by annotating a top-level test
class with `@SpringExtensionConfig(useTestClassScopedExtensionContext = true)`.
Alternatively, you can change the global default by setting the
`spring.test.extension.context.scope` property to `test_class`. The property is resolved
first via the
{spring-framework-api}/org/springframework/core/SpringProperties.html[`SpringProperties`]
mechanism (in a `spring.properties` file on the classpath or via JVM system properties,
for example `-Dspring.test.extension.context.scope=test_class`). If the Spring property
has not been set, the `SpringExtension` will attempt to resolve the property as a
https://docs.junit.org/current/running-tests/configuration-parameters.html[JUnit Platform configuration parameter]
as a fallback mechanism. If the property has not been set via either of those mechanisms,
the `SpringExtension` will use a test-method scoped extension context by default. Note,
however, that a `@SpringExtensionConfig` declaration always takes precedence over this
property.
[TIP]
====
If your top-level test class is configured to use JUnit Jupiter's
`@TestInstance(Lifecycle.PER_CLASS)` semantics, the `SpringExtension` will always use a
test-class scoped `ExtensionContext`, and there is no need to declare
`@SpringExtensionConfig(useTestClassScopedExtensionContext = true)`.
If a test class uses JUnit Jupiter's `@TestInstance(Lifecycle.PER_CLASS)` semantics, the
`SpringExtension` will always use a test-class scoped `ExtensionContext`, and
configuration via `@SpringExtensionConfig(useTestClassScopedExtensionContext = true)` or
the `spring.test.extension.context.scope` property will have no effect for that test
class.
====
[NOTE]
====
This annotation is currently only applicable to `@Nested` test class hierarchies and
should be applied to the top-level enclosing class of a `@Nested` test class hierarchy.
Consequently, there is no need to declare this annotation on a test class that does not
contain `@Nested` test classes.
====
[NOTE]
====
In addition,
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-nestedtestconfiguration[`@NestedTestConfiguration`]
does not apply to this annotation.
`@SpringExtensionConfig` will always be detected within a `@Nested` test class hierarchy,
effectively disregarding any `@NestedTestConfiguration(OVERRIDE)` declarations.
does not apply to this annotation. `@SpringExtensionConfig` will always be detected
within a `@Nested` test class hierarchy, effectively disregarding any
`@NestedTestConfiguration(OVERRIDE)` declarations.
====
[[integration-testing-annotations-junit-jupiter-springjunitconfig]]
@@ -396,11 +396,10 @@ recursively.
====
As of Spring Framework 7.0, the `SpringExtension` uses a test-method scoped
`ExtensionContext` within `@Nested` test class hierarchies by default. However, the
`SpringExtension` can be configured to use a test-class scoped `ExtensionContext`.
See the documentation for
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-springextensionconfig[`@SpringExtensionConfig`]
for details.
`SpringExtension` can be configured to use a test-class scoped `ExtensionContext` — for
example via `@SpringExtensionConfig` or the `spring.test.extension.context.scope` Spring
property (see
xref:testing/annotations/integration-junit-jupiter.adoc#integration-testing-annotations-springextensionconfig[`@SpringExtensionConfig`]).
====
[TIP]
@@ -87,8 +87,7 @@ public @interface TestConstructor {
* semantics by default.
* <p>May alternatively be configured via the
* {@link org.springframework.core.SpringProperties SpringProperties}
* mechanism.
* <p>This property may also be configured as a
* mechanism or as a
* <a href="https://docs.junit.org/current/running-tests/configuration-parameters.html">JUnit
* Platform configuration parameter</a>.
* @see #autowireMode
@@ -23,6 +23,7 @@ import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.AfterAll;
@@ -42,12 +43,14 @@ import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.jupiter.api.extension.TestInstancePostProcessor;
import org.junit.jupiter.api.extension.TestInstantiationAwareExtension;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.junit.platform.commons.annotation.Testable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.ParameterResolutionDelegate;
import org.springframework.context.ApplicationContext;
import org.springframework.core.SpringProperties;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
@@ -61,9 +64,9 @@ import org.springframework.test.context.support.PropertyProvider;
import org.springframework.test.context.support.TestConstructorUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentLruCache;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodFilter;
import org.springframework.util.StringUtils;
/**
* {@code SpringExtension} integrates the <em>Spring TestContext Framework</em>
@@ -85,15 +88,15 @@ import org.springframework.util.ReflectionUtils.MethodFilter;
* TestExecutionListener} is not compatible with the semantics associated with
* a test-method scoped extension context &mdash; or if a developer wishes to
* switch to test-class scoped semantics &mdash; the {@code SpringExtension} can
* be configured to use a test-class scoped extension context by annotating a
* top-level test class with
* be configured by annotating a top-level test class with
* {@link SpringExtensionConfig#useTestClassScopedExtensionContext()
* &#64;SpringExtensionConfig(useTestClassScopedExtensionContext = true)}. Note,
* however, that the {@code SpringExtension} will always use a test-class scoped
* {@code ExtensionContext} if your top-level test class is configured to use JUnit
* Jupiters {@code @TestInstance(Lifecycle.PER_CLASS)} semantics, in which case
* there is no need to declare
* {@code @SpringExtensionConfig(useTestClassScopedExtensionContext = true)}.
* &#64;SpringExtensionConfig(useTestClassScopedExtensionContext = true)} or by
* setting the {@value #EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME} property to
* {@link ExtensionContextScope#TEST_CLASS test_class}. Note that an explicit
* {@code @SpringExtensionConfig} declaration overrides the globally configured
* property. Furthermore, the {@code SpringExtension} will always use a test-class
* scoped {@code ExtensionContext} for a test class that is configured to use JUnit
* Jupiters {@code @TestInstance(Lifecycle.PER_CLASS)} semantics.
*
* <p><strong>NOTE:</strong> This class requires JUnit Jupiter 6.0 or higher.
*
@@ -112,12 +115,42 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
BeforeEachCallback, AfterEachCallback, BeforeTestExecutionCallback, AfterTestExecutionCallback,
ParameterResolver {
/**
* JVM system property used to configure the default {@link ExtensionContextScope}
* for the {@code SpringExtension}: {@value}.
* <p>Acceptable values include enum constants defined in {@code ExtensionContextScope},
* ignoring case. For example, the default may be changed to
* {@link ExtensionContextScope#TEST_CLASS} by supplying the following JVM
* system property via the command line.
* <pre style="code">-Dspring.test.extension.context.scope=test_class</pre>
* <p>If the property is not set, {@link ExtensionContextScope#TEST_METHOD}
* semantics will apply. Note, however, that {@code @SpringExtensionConfig}
* takes precedence over this property.
* <p>May alternatively be configured via the
* {@link org.springframework.core.SpringProperties SpringProperties}
* mechanism or as a
* <a href="https://docs.junit.org/current/running-tests/configuration-parameters.html">JUnit
* Platform configuration parameter</a>.
* @since 7.0.7
* @see ExtensionContextScope
* @see SpringExtensionConfig
*/
public static final String EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME = "spring.test.extension.context.scope";
/**
* {@link Namespace} in which {@code TestContextManagers} are stored, keyed
* by test class.
*/
private static final Namespace TEST_CONTEXT_MANAGER_NAMESPACE = Namespace.create(SpringExtension.class);
/**
* {@link Namespace} in which the resolved default {@link ExtensionContextScope}
* is stored.
* @since 7.0.7
*/
private static final Namespace DEFAULT_EXTENSION_CONTEXT_SCOPE_NAMESPACE =
Namespace.create(SpringExtension.class.getName() + "#default.extension.context.scope");
/**
* {@link Namespace} in which {@code @Autowired} validation error messages
* are stored, keyed by test class.
@@ -139,14 +172,6 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
private static final Namespace RECORD_APPLICATION_EVENTS_VALIDATION_NAMESPACE =
Namespace.create(SpringExtension.class.getName() + "#recordApplicationEvents.validation");
/**
* LRU cache for {@link SpringExtensionConfig#useTestClassScopedExtensionContext()}
* mappings, keyed by test class.
* @since 7.0
*/
private static final ConcurrentLruCache<Class<?>, Boolean> useTestClassScopedExtensionContextCache =
new ConcurrentLruCache<>(32, SpringExtension::useTestClassScopedExtensionContext);
// Note that @Test, @TestFactory, @TestTemplate, @RepeatedTest, and @ParameterizedTest
// are all meta-annotated with @Testable.
private static final List<Class<? extends Annotation>> JUPITER_ANNOTATION_TYPES =
@@ -157,16 +182,23 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
/**
* Returns {@link ExtensionContextScope#TEST_METHOD ExtensionContextScope.TEST_METHOD}.
* <p>This can be effectively overridden by annotating a test class with
* {@code @SpringExtensionConfig(useTestClassScopedExtensionContext = true)}.
* See the {@linkplain SpringExtension class-level Javadoc} for further details.
* Returns {@link TestInstantiationAwareExtension.ExtensionContextScope#TEST_METHOD
* ExtensionContextScope.TEST_METHOD}.
* <p>This can be overridden <em>locally</em> via the
* {@link SpringExtensionConfig @SpringExtensionConfig} annotation or
* <em>globally</em> via the {@value #EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME}
* property. See the {@linkplain SpringExtension class-level Javadoc} for further
* details.
* @since 7.0
* @see SpringExtensionConfig#useTestClassScopedExtensionContext()
* @see #EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME
* @see ExtensionContextScope
*/
@Override
public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) {
return ExtensionContextScope.TEST_METHOD;
public TestInstantiationAwareExtension.ExtensionContextScope getTestInstantiationExtensionContextScope(
ExtensionContext rootContext) {
return TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD;
}
/**
@@ -464,20 +496,19 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
}
/**
* Find the properly {@linkplain ExtensionContextScope scoped} {@link ExtensionContext}
* for the supplied test class.
* Find the properly scoped {@link ExtensionContext} for the supplied test class.
* <p>If the supplied {@code ExtensionContext} is already properly scoped, it
* will be returned. Otherwise, if the test class is annotated with
* {@code @SpringExtensionConfig(useTestClassScopedExtensionContext = true)},
* this method searches the {@code ExtensionContext} hierarchy for an
* {@code ExtensionContext} whose test class is the same as the supplied
* test class.
* will be returned. Otherwise, if test-class scoped semantics apply (see
* {@linkplain SpringExtension class-level Javadoc}), this method searches the
* {@code ExtensionContext} hierarchy for an {@code ExtensionContext} whose test
* class is the same as the supplied test class.
* @since 7.0
* @see SpringExtensionConfig#useTestClassScopedExtensionContext()
* @see #EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME
* @see ExtensionContextScope
*/
private static ExtensionContext findProperlyScopedExtensionContext(Class<?> testClass, ExtensionContext context) {
if (useTestClassScopedExtensionContextCache.get(testClass)) {
if (shouldUseTestClassScopedExtensionContext(testClass, context)) {
while (context.getRequiredTestClass() != testClass) {
context = context.getParent().get();
}
@@ -486,13 +517,11 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
}
/**
* Determine if the supplied test class, or one of its enclosing classes, is annotated
* with {@code @SpringExtensionConfig(useTestClassScopedExtensionContext = true)}.
* Determine whether test-class scoped {@code ExtensionContext} semantics apply
* for the supplied test class.
* @since 7.0
* @see SpringExtensionConfig#useTestClassScopedExtensionContext()
* @see #useTestClassScopedExtensionContextCache
*/
private static boolean useTestClassScopedExtensionContext(Class<?> testClass) {
private static boolean shouldUseTestClassScopedExtensionContext(Class<?> testClass, ExtensionContext context) {
MergedAnnotation<SpringExtensionConfig> mergedAnnotation =
MergedAnnotations.search(SearchStrategy.TYPE_HIERARCHY)
.withEnclosingClasses(ClassUtils::isInnerClass)
@@ -509,7 +538,96 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
return mergedAnnotation.getBoolean("useTestClassScopedExtensionContext");
}
return false;
return (resolveDefaultExtensionContextScope(context) == ExtensionContextScope.TEST_CLASS);
}
/**
* Resolve the default {@link ExtensionContextScope} from the
* {@value #EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME} property, first via
* {@link SpringProperties} and then via
* {@link ExtensionContext#getConfigurationParameter(String)} as a fallback
* strategy if the Spring property is not set.
* @param context the current {@code ExtensionContext}
* @return the resolved scope, or {@link ExtensionContextScope#TEST_METHOD}
* if the property is not set
* @since 7.0.7
*/
private static ExtensionContextScope resolveDefaultExtensionContextScope(ExtensionContext context) {
return context.getRoot().getStore(DEFAULT_EXTENSION_CONTEXT_SCOPE_NAMESPACE)
.computeIfAbsent(ExtensionContextScope.class, key -> {
String springValue = SpringProperties.getProperty(EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME);
ExtensionContextScope scope = ExtensionContextScope.from(springValue);
if (scope != null) {
return scope;
}
rejectUnsupportedScope(springValue);
String junitValue = context.getConfigurationParameter(EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME)
.orElse(null);
scope = ExtensionContextScope.from(junitValue);
if (scope != null) {
return scope;
}
rejectUnsupportedScope(junitValue);
// Default to test-method scope.
return ExtensionContextScope.TEST_METHOD;
}, ExtensionContextScope.class);
}
private static void rejectUnsupportedScope(@Nullable String scope) {
if (StringUtils.hasText(scope)) {
throw new IllegalArgumentException("Unsupported value '%s' for property '%s'"
.formatted(scope.strip(), EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME));
}
}
/**
* Enumeration of <em>extension context scopes</em> for configuring how the
* {@link SpringExtension} resolves an {@link ExtensionContext} within
* {@code @Nested} test class hierarchies.
*
* @since 7.0.7
* @see SpringExtension#EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME
* @see SpringExtensionConfig
* @see org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope
*/
public enum ExtensionContextScope {
/**
* Use a test-method scoped {@link ExtensionContext} within {@code @Nested}
* test class hierarchies.
* @see org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope#TEST_METHOD
*/
TEST_METHOD,
/**
* Use a test-class scoped {@link ExtensionContext} within {@code @Nested}
* test class hierarchies.
* @see org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope#DEFAULT
*/
TEST_CLASS;
/**
* Get the {@link ExtensionContextScope} enum constant with the supplied name,
* {@linkplain String#strip() stripped} and ignoring case.
* @param name the name of the enum constant to retrieve
* @return the corresponding enum constant, or {@code null} if not found
* @see ExtensionContextScope#valueOf(String)
*/
static @Nullable ExtensionContextScope from(@Nullable String name) {
if (!StringUtils.hasText(name)) {
return null;
}
try {
return ExtensionContextScope.valueOf(name.strip().toUpperCase(Locale.ROOT));
}
catch (IllegalArgumentException ex) {
return null;
}
}
}
}
@@ -65,8 +65,12 @@ public @interface SpringExtensionConfig {
* will always use a test-class scoped {@code ExtensionContext}, and there is no need
* to declare {@code @SpringExtensionConfig(useTestClassScopedExtensionContext = true)}.
*
* <p>Furthermore, this attribute takes precedence over global configuration
* of the {@code spring.test.extension.context.scope} property.
*
* @see SpringExtension
* @see SpringExtension#getTestInstantiationExtensionContextScope(org.junit.jupiter.api.extension.ExtensionContext)
* @see SpringExtension#EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME
* @see SpringExtension.ExtensionContextScope
*/
boolean useTestClassScopedExtensionContext();
@@ -0,0 +1,208 @@
/*
* Copyright 2002-present 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.junit.jupiter;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.platform.testkit.engine.EngineTestKit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.SpringProperties;
import org.springframework.core.env.Environment;
import org.springframework.test.context.NestedTestConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension.ExtensionContextScope;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE;
import static org.springframework.test.context.junit.jupiter.SpringExtension.EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME;
import static org.springframework.test.context.junit.jupiter.SpringExtension.ExtensionContextScope.TEST_CLASS;
import static org.springframework.test.context.junit.jupiter.SpringExtension.ExtensionContextScope.TEST_METHOD;
/**
* Tests for {@link SpringExtension.ExtensionContextScope} and
* {@link SpringExtension#EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME}.
*
* @author Sam Brannen
* @since 7.0.7
*/
class SpringExtensionExtensionContextScopeTests {
@Test
void extensionContextScopeFromString() {
assertThat(ExtensionContextScope.from(null)).isNull();
assertThat(ExtensionContextScope.from("")).isNull();
assertThat(ExtensionContextScope.from(" ")).isNull();
assertThat(ExtensionContextScope.from("bogus")).isNull();
assertThat(ExtensionContextScope.from("TEST_METHOD")).isSameAs(TEST_METHOD);
assertThat(ExtensionContextScope.from("test_method")).isSameAs(TEST_METHOD);
assertThat(ExtensionContextScope.from("Test_Method")).isSameAs(TEST_METHOD);
assertThat(ExtensionContextScope.from("TEST_CLASS")).isSameAs(TEST_CLASS);
assertThat(ExtensionContextScope.from("test_class")).isSameAs(TEST_CLASS);
assertThat(ExtensionContextScope.from("Test_Class")).isSameAs(TEST_CLASS);
}
@Test
void invalidExtensionContextScopeIsRejectedWhenConfiguredViaSpringProperties() {
SpringProperties.setProperty(EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME, "bogus");
try {
EngineTestKit.engine("junit-jupiter")
.selectors(selectClass(InvalidScopeTestCase.class))
.execute()
.testEvents()
.assertStatistics(stats -> stats.started(1).failed(1));
}
finally {
SpringProperties.setProperty(EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME, null);
}
}
@Test
void invalidExtensionContextScopeIsRejectedWhenConfiguredViaJUnitConfigurationParameter() {
EngineTestKit.engine("junit-jupiter")
.selectors(selectClass(InvalidScopeTestCase.class))
.configurationParameter(EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME, "bogus")
.execute()
.testEvents()
.assertStatistics(stats -> stats.started(1).failed(1));
}
@Test
void testClassScopeConfiguredViaSpringProperties() {
SpringProperties.setProperty(EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME, TEST_CLASS.name());
try {
var results = EngineTestKit.engine("junit-jupiter")
.selectors(selectClass(GlobalClassScopedConfigurationTestCase.class))
.execute();
results.containerEvents()
.assertStatistics(stats -> stats.started(3).succeeded(3).failed(0));
results.testEvents()
.assertStatistics(stats -> stats.started(2).succeeded(2).failed(0));
}
finally {
SpringProperties.setProperty(EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME, null);
}
}
@Test
void testClassScopeConfiguredViaJUnitConfigurationParameter() {
var results = EngineTestKit.engine("junit-jupiter")
.selectors(selectClass(GlobalClassScopedConfigurationTestCase.class))
.configurationParameter(EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME, TEST_CLASS.name())
.execute();
results.containerEvents()
.assertStatistics(stats -> stats.started(3).succeeded(3).failed(0));
results.testEvents()
.assertStatistics(stats -> stats.started(2).succeeded(2).failed(0));
}
@Test
void springExtensionConfigOverridesGlobalTestClassScopeConfiguration() {
EngineTestKit.engine("junit-jupiter")
.selectors(selectClass(SpringExtensionConfigOverridesGlobalPropertyTestCase.class))
.configurationParameter(EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME, TEST_CLASS.name())
.execute()
.testEvents()
.assertStatistics(stats -> stats.started(2).succeeded(2).failed(0));
}
@SpringJUnitConfig
static class InvalidScopeTestCase {
@Test
void test() {
// no-op
}
}
@SpringJUnitConfig
@TestPropertySource(properties = "p1 = v1")
@NestedTestConfiguration(OVERRIDE)
@FailingTestCase
static class GlobalClassScopedConfigurationTestCase {
@Autowired
Environment env1;
@Test
void propertiesInEnvironment() {
assertThat(env1.getProperty("p1")).isEqualTo("v1");
}
@Nested
@SpringJUnitConfig(Config.class)
@TestPropertySource(properties = "p2 = v2")
class ConfigOverriddenByDefaultTests {
@Autowired
Environment env2;
@Test
void propertiesInEnvironment() {
assertThat(env1.getProperty("p1")).isEqualTo("v1");
assertThat(env1).isNotSameAs(env2);
assertThat(env2.getProperty("p1")).isNull();
assertThat(env2.getProperty("p2")).isEqualTo("v2");
}
}
@Configuration
static class Config {
}
}
@SpringJUnitConfig
@SpringExtensionConfig(useTestClassScopedExtensionContext = false)
@TestPropertySource(properties = "p1 = v1")
@NestedTestConfiguration(OVERRIDE)
static class SpringExtensionConfigOverridesGlobalPropertyTestCase {
@Autowired
Environment env1;
@Test
void propertiesInEnvironment() {
assertThat(env1.getProperty("p1")).isEqualTo("v1");
}
@Nested
@SpringJUnitConfig(Config.class)
@TestPropertySource(properties = "p2 = v2")
class ConfigOverriddenByDefaultTests {
@Autowired
Environment env2;
@Test
void propertiesInEnvironment() {
assertThat(env1).isSameAs(env2);
assertThat(env2.getProperty("p1")).isNull();
assertThat(env2.getProperty("p2")).isEqualTo("v2");
}
}
@Configuration
static class Config {
}
}
}