From d24a31d469a43e49c4100533d01e488c357a1d69 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Tue, 21 Oct 2025 15:34:11 +0200 Subject: [PATCH 1/2] Support JUnit Jupiter ExtensionContextScope.TEST_METHOD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Historically, @⁠Autowired fields in an enclosing class of a @⁠Nested test class have been injected from the ApplicationContext for the enclosing class. If the enclosing test class and @⁠Nested test class share the same ApplicationContext configuration, things work as developers expect. However, if the enclosing class and @⁠Nested test class have different ApplicationContexts, that can lead to difficult-to-debug scenarios. For example, a bean injected into the enclosing test class will not participate in a test-managed transaction in the @⁠Nested test class (see gh-34576). JUnit Jupiter 5.12 introduced a new ExtensionContextScope feature which allows the SpringExtension to behave the same for @⁠Autowired fields as it already does for @⁠Autowired arguments in lifecycle and test methods. Specifically, if a developer sets the ExtensionContextScope to TEST_METHOD — for example, by configuring the following configuration parameter as a JVM system property or in a `junit-platform.properties` file — the SpringExtension already supports dependency injection from the current, @⁠Nested ApplicationContext in @⁠Autowired fields in an enclosing class of the @⁠Nested test class. junit.jupiter.extensions.testinstantiation.extensioncontextscope.default=test_method However, there are two scenarios that fail as of Spring Framework 6.2.12. 1. @⁠TestConstructor configuration in @⁠Nested class hierarchies. 2. Field injection for bean overrides (such as @⁠MockitoBean) in @⁠Nested class hierarchies. Commit 82c34f7b51 fixed the SpringExtension to support scenario #2 above. To fix scenario #1, this commit revises BeanOverrideTestExecutionListener's injectField() implementation to look up the fields to inject for the "current test instance" instead of for the "current test class". This commit also introduces tests for both scenarios. See gh-34576 See gh-35676 Closes gh-35680 --- .../BeanOverrideTestExecutionListener.java | 12 +- ...opedExtensionContextIntegrationTests.java} | 6 +- ...copedExtensionContextIntegrationTests.java | 205 ++++++++++++++ ...opedExtensionContextIntegrationTests.java} | 6 +- ...copedExtensionContextIntegrationTests.java | 164 +++++++++++ ...opedExtensionContextIntegrationTests.java} | 8 +- ...copedExtensionContextIntegrationTests.java | 126 +++++++++ ...assScopedExtensionContextNestedTests.java} | 13 +- ...thodScopedExtensionContextNestedTests.java | 236 ++++++++++++++++ ...assScopedExtensionContextNestedTests.java} | 14 +- ...thodScopedExtensionContextNestedTests.java | 173 ++++++++++++ ...assScopedExtensionContextNestedTests.java} | 10 +- ...thodScopedExtensionContextNestedTests.java | 234 ++++++++++++++++ ...assScopedExtensionContextNestedTests.java} | 14 +- ...thodScopedExtensionContextNestedTests.java | 261 ++++++++++++++++++ ...assScopedExtensionContextNestedTests.java} | 8 +- ...thodScopedExtensionContextNestedTests.java | 190 +++++++++++++ ...assScopedExtensionContextNestedTests.java} | 18 +- ...thodScopedExtensionContextNestedTests.java | 198 +++++++++++++ .../WebAppConfigurationNestedTests.java | 3 +- .../NestedTestsWithSpringRulesTests.java | 3 +- 21 files changed, 1859 insertions(+), 43 deletions(-) rename spring-test/src/test/java/org/springframework/test/context/bean/override/convention/{TestBeanByNameLookupIntegrationTests.java => TestBeanByNameLookupTestClassScopedExtensionContextIntegrationTests.java} (96%) create mode 100644 spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanByNameLookupTestMethodScopedExtensionContextIntegrationTests.java rename spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/{MockitoBeanByNameLookupIntegrationTests.java => MockitoBeanByNameLookupTestClassScopedExtensionContextIntegrationTests.java} (94%) create mode 100644 spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanByNameLookupTestMethodScopedExtensionContextIntegrationTests.java rename spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/{MockitoSpyBeanByNameLookupIntegrationTests.java => MockitoSpyBeanByNameLookupTestClassScopedExtensionContextIntegrationTests.java} (90%) create mode 100644 spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanByNameLookupTestMethodScopedExtensionContextIntegrationTests.java rename spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/{ActiveProfilesNestedTests.java => ActiveProfilesTestClassScopedExtensionContextNestedTests.java} (91%) create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ActiveProfilesTestMethodScopedExtensionContextNestedTests.java rename spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/{ConstructorInjectionNestedTests.java => ConstructorInjectionTestClassScopedExtensionContextNestedTests.java} (86%) create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ConstructorInjectionTestMethodScopedExtensionContextNestedTests.java rename spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/{ContextConfigurationNestedTests.java => ContextConfigurationTestClassScopedExtensionContextNestedTests.java} (92%) create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextConfigurationTestMethodScopedExtensionContextNestedTests.java rename spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/{ContextHierarchyNestedTests.java => ContextHierarchyTestClassScopedExtensionContextNestedTests.java} (92%) create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextHierarchyTestMethodScopedExtensionContextNestedTests.java rename spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/{TestConstructorNestedTests.java => TestConstructorTestClassScopedExtensionContextNestedTests.java} (92%) create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestConstructorTestMethodScopedExtensionContextNestedTests.java rename spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/{TestPropertySourceNestedTests.java => TestPropertySourceTestClassScopedExtensionContextNestedTests.java} (90%) create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestPropertySourceTestMethodScopedExtensionContextNestedTests.java diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideTestExecutionListener.java index 089f5a64801..7237b8cfbaf 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideTestExecutionListener.java @@ -93,9 +93,17 @@ public class BeanOverrideTestExecutionListener extends AbstractTestExecutionList * a corresponding bean override instance. */ private static void injectFields(TestContext testContext) { - List handlers = BeanOverrideHandler.forTestClass(testContext.getTestClass()); + Object testInstance = testContext.getTestInstance(); + // Since JUnit Jupiter 5.12, if the SpringExtension is used with Jupiter's + // ExtensionContextScope.TEST_METHOD mode, the value returned from + // testContext.getTestClass() may refer to the declaring class of the test + // method which is about to be invoked (which may be in a @Nested class + // within the class for the test instance). Thus, we use the class for the + // test instance as the "test class". + Class testClass = testInstance.getClass(); + + List handlers = BeanOverrideHandler.forTestClass(testClass); if (!handlers.isEmpty()) { - Object testInstance = testContext.getTestInstance(); ApplicationContext applicationContext = testContext.getApplicationContext(); Assert.state(applicationContext.containsBean(BeanOverrideRegistry.BEAN_NAME), () -> """ diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanByNameLookupIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanByNameLookupTestClassScopedExtensionContextIntegrationTests.java similarity index 96% rename from spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanByNameLookupIntegrationTests.java rename to spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanByNameLookupTestClassScopedExtensionContextIntegrationTests.java index 99192e9fcdd..2eaeb7b47e3 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanByNameLookupIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanByNameLookupTestClassScopedExtensionContextIntegrationTests.java @@ -19,6 +19,7 @@ package org.springframework.test.context.bean.override.convention; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -28,14 +29,15 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import static org.assertj.core.api.Assertions.assertThat; /** - * Integration tests for {@link TestBean} that use by-name lookup. + * Integration tests for {@link TestBean} that use by-name lookup with test class + * {@link ExtensionContextScope}. * * @author Simon Baslé * @author Sam Brannen * @since 6.2 */ @SpringJUnitConfig -public class TestBeanByNameLookupIntegrationTests { +public class TestBeanByNameLookupTestClassScopedExtensionContextIntegrationTests { @TestBean(name = "field") String field; diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanByNameLookupTestMethodScopedExtensionContextIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanByNameLookupTestMethodScopedExtensionContextIntegrationTests.java new file mode 100644 index 00000000000..2b41f116f46 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanByNameLookupTestMethodScopedExtensionContextIntegrationTests.java @@ -0,0 +1,205 @@ +/* + * 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.bean.override.convention; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; +import org.junit.platform.testkit.engine.EngineTestKit; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME; +import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; + + +/** + * Integration tests for {@link TestBean} that use by-name lookup with + * {@link ExtensionContextScope#TEST_METHOD}. + * + * @author Simon Baslé + * @author Sam Brannen + * @since 6.2.13 + */ +public class TestBeanByNameLookupTestMethodScopedExtensionContextIntegrationTests { + + @Test + void runTests() { + EngineTestKit.engine("junit-jupiter") + .configurationParameter(DEFAULT_SCOPE_PROPERTY_NAME, TEST_METHOD.name()) + .selectors(selectClass(TestCase.class)) + .execute() + .testEvents() + .assertStatistics(stats -> stats.started(12).succeeded(12).failed(0)); + } + + + @SpringJUnitConfig + public static class TestCase { + + @TestBean(name = "field") + String field; + + @TestBean(name = "methodRenamed1", methodName = "field") + String methodRenamed1; + + static String field() { + return "fieldOverride"; + } + + static String nestedField() { + return "nestedFieldOverride"; + } + + @Test + void fieldHasOverride(ApplicationContext ctx) { + assertThat(ctx.getBean("field")).as("applicationContext").isEqualTo("fieldOverride"); + assertThat(field).as("injection point").isEqualTo("fieldOverride"); + } + + @Test + void fieldWithMethodNameHasOverride(ApplicationContext ctx) { + assertThat(ctx.getBean("methodRenamed1")).as("applicationContext").isEqualTo("fieldOverride"); + assertThat(methodRenamed1).as("injection point").isEqualTo("fieldOverride"); + } + + + @Nested + @DisplayName("With @TestBean in enclosing class and in @Nested class") + public class TestBeanFieldInEnclosingClassTestCase { + + @TestBean(name = "nestedField") + String nestedField; + + @TestBean(name = "methodRenamed2", methodName = "nestedField") + String methodRenamed2; + + + @Test + void fieldHasOverride(ApplicationContext ctx) { + assertThat(ctx.getBean("field")).as("applicationContext").isEqualTo("fieldOverride"); + assertThat(field).as("injection point").isEqualTo("fieldOverride"); + } + + @Test + void fieldWithMethodNameHasOverride(ApplicationContext ctx) { + assertThat(ctx.getBean("methodRenamed1")).as("applicationContext").isEqualTo("fieldOverride"); + assertThat(methodRenamed1).as("injection point").isEqualTo("fieldOverride"); + } + + @Test + void nestedFieldHasOverride(ApplicationContext ctx) { + assertThat(ctx.getBean("nestedField")).as("applicationContext").isEqualTo("nestedFieldOverride"); + assertThat(nestedField).isEqualTo("nestedFieldOverride"); + } + + @Test + void nestedFieldWithMethodNameHasOverride(ApplicationContext ctx) { + assertThat(ctx.getBean("methodRenamed2")).as("applicationContext").isEqualTo("nestedFieldOverride"); + assertThat(methodRenamed2).isEqualTo("nestedFieldOverride"); + } + + @Nested + @DisplayName("With @TestBean in the enclosing classes") + public class TestBeanFieldInEnclosingClassLevel2TestCase { + + @Test + void fieldHasOverride(ApplicationContext ctx) { + assertThat(ctx.getBean("field")).as("applicationContext").isEqualTo("fieldOverride"); + assertThat(field).as("injection point").isEqualTo("fieldOverride"); + } + + @Test + void fieldWithMethodNameHasOverride(ApplicationContext ctx) { + assertThat(ctx.getBean("methodRenamed1")).as("applicationContext").isEqualTo("fieldOverride"); + assertThat(methodRenamed1).as("injection point").isEqualTo("fieldOverride"); + } + + @Test + void nestedFieldHasOverride(ApplicationContext ctx) { + assertThat(ctx.getBean("nestedField")).as("applicationContext").isEqualTo("nestedFieldOverride"); + assertThat(nestedField).isEqualTo("nestedFieldOverride"); + } + + @Test + void nestedFieldWithMethodNameHasOverride(ApplicationContext ctx) { + assertThat(ctx.getBean("methodRenamed2")).as("applicationContext").isEqualTo("nestedFieldOverride"); + assertThat(methodRenamed2).isEqualTo("nestedFieldOverride"); + } + } + } + + @Nested + @DisplayName("With factory method in enclosing class") + public class TestBeanFactoryMethodInEnclosingClassTestCase { + + @TestBean(methodName = "nestedField", name = "nestedField") + String nestedField; + + @Test + void nestedFieldHasOverride(ApplicationContext ctx) { + assertThat(ctx.getBean("nestedField")).as("applicationContext").isEqualTo("nestedFieldOverride"); + assertThat(nestedField).isEqualTo("nestedFieldOverride"); + } + + @Nested + @DisplayName("With factory method in the enclosing class of the enclosing class") + public class TestBeanFactoryMethodInEnclosingClassLevel2TestCase { + + @TestBean(methodName = "nestedField", name = "nestedNestedField") + String nestedNestedField; + + @Test + void nestedFieldHasOverride(ApplicationContext ctx) { + assertThat(ctx.getBean("nestedNestedField")).as("applicationContext").isEqualTo("nestedFieldOverride"); + assertThat(nestedNestedField).isEqualTo("nestedFieldOverride"); + } + } + } + } + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean("field") + String bean1() { + return "prod"; + } + + @Bean("nestedField") + String bean2() { + return "nestedProd"; + } + + @Bean("methodRenamed1") + String bean3() { + return "Prod"; + } + + @Bean("methodRenamed2") + String bean4() { + return "NestedProd"; + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanByNameLookupIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanByNameLookupTestClassScopedExtensionContextIntegrationTests.java similarity index 94% rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanByNameLookupIntegrationTests.java rename to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanByNameLookupTestClassScopedExtensionContextIntegrationTests.java index 2fa39d4bbec..5f38b70d1a8 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanByNameLookupIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanByNameLookupTestClassScopedExtensionContextIntegrationTests.java @@ -19,6 +19,7 @@ package org.springframework.test.context.bean.override.mockito; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -33,14 +34,15 @@ import org.springframework.test.mockito.MockitoAssertions; import static org.assertj.core.api.Assertions.assertThat; /** - * Integration tests for {@link MockitoBean} that use by-name lookup. + * Integration tests for {@link MockitoBean} that use by-name lookup with test class + * {@link ExtensionContextScope}. * * @author Simon Baslé * @author Sam Brannen * @since 6.2 */ @SpringJUnitConfig -public class MockitoBeanByNameLookupIntegrationTests { +public class MockitoBeanByNameLookupTestClassScopedExtensionContextIntegrationTests { @MockitoBean("field") ExampleService field; diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanByNameLookupTestMethodScopedExtensionContextIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanByNameLookupTestMethodScopedExtensionContextIntegrationTests.java new file mode 100644 index 00000000000..e2c5986d948 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanByNameLookupTestMethodScopedExtensionContextIntegrationTests.java @@ -0,0 +1,164 @@ +/* + * 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.bean.override.mockito; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; +import org.junit.platform.testkit.engine.EngineTestKit; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.bean.override.example.ExampleService; +import org.springframework.test.context.bean.override.example.RealExampleService; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.springframework.test.mockito.MockitoAssertions; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME; +import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; + +/** + * Integration tests for {@link MockitoBean} that use by-name lookup with + * {@link ExtensionContextScope#TEST_METHOD}. + * + * @author Simon Baslé + * @author Sam Brannen + * @since 6.2.13 + */ +public class MockitoBeanByNameLookupTestMethodScopedExtensionContextIntegrationTests { + + @Test + void runTests() { + EngineTestKit.engine("junit-jupiter") + .configurationParameter(DEFAULT_SCOPE_PROPERTY_NAME, TEST_METHOD.name()) + .selectors(selectClass(TestCase.class)) + .execute() + .testEvents() + .assertStatistics(stats -> stats.started(6).succeeded(6).failed(0)); + } + + + @SpringJUnitConfig + public static class TestCase { + + @MockitoBean("field") + ExampleService field; + + @MockitoBean("nonExistingBean") + ExampleService nonExisting; + + + @Test + void fieldAndRenamedFieldHaveSameOverride(ApplicationContext ctx) { + assertThat(ctx.getBean("field")) + .isInstanceOf(ExampleService.class) + .satisfies(MockitoAssertions::assertIsMock) + .isSameAs(field); + + assertThat(field.greeting()).as("mocked greeting").isNull(); + } + + @Test + void fieldIsMockedWhenNoOriginalBean(ApplicationContext ctx) { + assertThat(ctx.getBean("nonExistingBean")) + .isInstanceOf(ExampleService.class) + .satisfies(MockitoAssertions::assertIsMock) + .isSameAs(nonExisting); + + assertThat(nonExisting.greeting()).as("mocked greeting").isNull(); + } + + + @Nested + @DisplayName("With @MockitoBean in enclosing class and in @Nested class") + public class MockitoBeanNestedTestCase { + + @Autowired + @Qualifier("field") + ExampleService localField; + + @Autowired + @Qualifier("nonExistingBean") + ExampleService localNonExisting; + + @MockitoBean("nestedField") + ExampleService nestedField; + + @MockitoBean("nestedNonExistingBean") + ExampleService nestedNonExisting; + + + @Test + void fieldAndRenamedFieldHaveSameOverride(ApplicationContext ctx) { + assertThat(ctx.getBean("field")) + .isInstanceOf(ExampleService.class) + .satisfies(MockitoAssertions::assertIsMock) + .isSameAs(localField); + + assertThat(localField.greeting()).as("mocked greeting").isNull(); + } + + @Test + void fieldIsMockedWhenNoOriginalBean(ApplicationContext ctx) { + assertThat(ctx.getBean("nonExistingBean")) + .isInstanceOf(ExampleService.class) + .satisfies(MockitoAssertions::assertIsMock) + .isSameAs(localNonExisting); + + assertThat(localNonExisting.greeting()).as("mocked greeting").isNull(); + } + + @Test + void nestedFieldAndRenamedFieldHaveSameOverride(ApplicationContext ctx) { + assertThat(ctx.getBean("nestedField")) + .isInstanceOf(ExampleService.class) + .satisfies(MockitoAssertions::assertIsMock) + .isSameAs(nestedField); + } + + @Test + void nestedFieldIsMockedWhenNoOriginalBean(ApplicationContext ctx) { + assertThat(ctx.getBean("nestedNonExistingBean")) + .isInstanceOf(ExampleService.class) + .satisfies(MockitoAssertions::assertIsMock) + .isSameAs(nestedNonExisting); + } + } + } + + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean("field") + ExampleService bean1() { + return new RealExampleService("Hello Field"); + } + + @Bean("nestedField") + ExampleService bean2() { + return new RealExampleService("Hello Nested Field"); + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanByNameLookupIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanByNameLookupTestClassScopedExtensionContextIntegrationTests.java similarity index 90% rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanByNameLookupIntegrationTests.java rename to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanByNameLookupTestClassScopedExtensionContextIntegrationTests.java index 9c79e593318..eeed395b38a 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanByNameLookupIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanByNameLookupTestClassScopedExtensionContextIntegrationTests.java @@ -19,6 +19,7 @@ package org.springframework.test.context.bean.override.mockito; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -27,21 +28,22 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.bean.override.example.ExampleService; import org.springframework.test.context.bean.override.example.RealExampleService; -import org.springframework.test.context.bean.override.mockito.MockitoSpyBeanByNameLookupIntegrationTests.Config; +import org.springframework.test.context.bean.override.mockito.MockitoSpyBeanByNameLookupTestClassScopedExtensionContextIntegrationTests.Config; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.mockito.MockitoAssertions; import static org.assertj.core.api.Assertions.assertThat; /** - * Integration tests for {@link MockitoSpyBean} that use by-name lookup. + * Integration tests for {@link MockitoSpyBean} that use by-name lookup with test class + * {@link ExtensionContextScope}. * * @author Simon Baslé * @author Sam Brannen * @since 6.2 */ @SpringJUnitConfig(Config.class) -public class MockitoSpyBeanByNameLookupIntegrationTests { +public class MockitoSpyBeanByNameLookupTestClassScopedExtensionContextIntegrationTests { @MockitoSpyBean("field1") ExampleService field; diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanByNameLookupTestMethodScopedExtensionContextIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanByNameLookupTestMethodScopedExtensionContextIntegrationTests.java new file mode 100644 index 00000000000..6aa660a9f5f --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanByNameLookupTestMethodScopedExtensionContextIntegrationTests.java @@ -0,0 +1,126 @@ +/* + * 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.bean.override.mockito; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; +import org.junit.platform.testkit.engine.EngineTestKit; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.bean.override.example.ExampleService; +import org.springframework.test.context.bean.override.example.RealExampleService; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.springframework.test.mockito.MockitoAssertions; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME; +import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; + +/** + * Integration tests for {@link MockitoSpyBean} that use by-name lookup with + * {@link ExtensionContextScope#TEST_METHOD}. + * + * @author Simon Baslé + * @author Sam Brannen + * @since 6.2.13 + */ +public class MockitoSpyBeanByNameLookupTestMethodScopedExtensionContextIntegrationTests { + + @Test + void runTests() { + EngineTestKit.engine("junit-jupiter") + .configurationParameter(DEFAULT_SCOPE_PROPERTY_NAME, TEST_METHOD.name()) + .selectors(selectClass(TestCase.class)) + .execute() + .testEvents() + .assertStatistics(stats -> stats.started(3).succeeded(3).failed(0)); + } + + + @SpringJUnitConfig(Config.class) + public static class TestCase { + + @MockitoSpyBean("field1") + ExampleService field; + + + @Test + void fieldHasOverride(ApplicationContext ctx) { + assertThat(ctx.getBean("field1")) + .isInstanceOf(ExampleService.class) + .satisfies(MockitoAssertions::assertIsSpy) + .isSameAs(field); + + assertThat(field.greeting()).isEqualTo("bean1"); + } + + + @Nested + @DisplayName("With @MockitoSpyBean in enclosing class and in @Nested class") + public class MockitoSpyBeanNestedTestCase { + + @Autowired + @Qualifier("field1") + ExampleService localField; + + @MockitoSpyBean("field2") + ExampleService nestedField; + + @Test + void fieldHasOverride(ApplicationContext ctx) { + assertThat(ctx.getBean("field1")) + .isInstanceOf(ExampleService.class) + .satisfies(MockitoAssertions::assertIsSpy) + .isSameAs(localField); + + assertThat(localField.greeting()).isEqualTo("bean1"); + } + + @Test + void nestedFieldHasOverride(ApplicationContext ctx) { + assertThat(ctx.getBean("field2")) + .isInstanceOf(ExampleService.class) + .satisfies(MockitoAssertions::assertIsSpy) + .isSameAs(nestedField); + + assertThat(nestedField.greeting()).isEqualTo("bean2"); + } + } + } + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean("field1") + ExampleService bean1() { + return new RealExampleService("bean1"); + } + + @Bean("field2") + ExampleService bean2() { + return new RealExampleService("bean2"); + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ActiveProfilesNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ActiveProfilesTestClassScopedExtensionContextNestedTests.java similarity index 91% rename from spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ActiveProfilesNestedTests.java rename to spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ActiveProfilesTestClassScopedExtensionContextNestedTests.java index 2ba54a1f766..1a80d5a8f3c 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ActiveProfilesNestedTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ActiveProfilesTestClassScopedExtensionContextNestedTests.java @@ -20,6 +20,7 @@ import java.util.List; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; @@ -30,7 +31,7 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.NestedTestConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; -import org.springframework.test.context.junit.jupiter.nested.ActiveProfilesNestedTests.Config1; +import org.springframework.test.context.junit.jupiter.nested.ActiveProfilesTestClassScopedExtensionContextNestedTests.Config1; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT; @@ -39,7 +40,8 @@ import static org.springframework.test.context.NestedTestConfiguration.Enclosing /** * Integration tests that verify support for {@code @Nested} test classes using * {@link ActiveProfiles @ActiveProfiles} in conjunction with the - * {@link SpringExtension} in a JUnit Jupiter environment. + * {@link SpringExtension} in a JUnit Jupiter environment with test class + * {@link ExtensionContextScope}. * * @author Sam Brannen * @since 5.3 @@ -47,7 +49,7 @@ import static org.springframework.test.context.NestedTestConfiguration.Enclosing @SpringJUnitConfig(Config1.class) @ActiveProfiles("1") @NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default -class ActiveProfilesNestedTests { +class ActiveProfilesTestClassScopedExtensionContextNestedTests { @Autowired List strings; @@ -69,8 +71,9 @@ class ActiveProfilesNestedTests { @Test void test() { - assertThat(strings).containsExactlyInAnyOrder("X", "A1"); - assertThat(this.localStrings).containsExactlyInAnyOrder("X", "A1"); + assertThat(strings) + .isEqualTo(this.localStrings) + .containsExactlyInAnyOrder("X", "A1"); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ActiveProfilesTestMethodScopedExtensionContextNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ActiveProfilesTestMethodScopedExtensionContextNestedTests.java new file mode 100644 index 00000000000..a8a73be4183 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ActiveProfilesTestMethodScopedExtensionContextNestedTests.java @@ -0,0 +1,236 @@ +/* + * 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.nested; + +import java.util.List; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; +import org.junit.platform.testkit.engine.EngineTestKit; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.NestedTestConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME; +import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE; + +/** + * Integration tests that verify support for {@code @Nested} test classes using + * {@link ActiveProfiles @ActiveProfiles} in conjunction with the + * {@link SpringExtension} in a JUnit Jupiter environment with + * {@link ExtensionContextScope#TEST_METHOD}. + * + * @author Sam Brannen + * @since 6.2.13 + */ +class ActiveProfilesTestMethodScopedExtensionContextNestedTests { + + @Test + void runTests() { + EngineTestKit.engine("junit-jupiter") + .configurationParameter(DEFAULT_SCOPE_PROPERTY_NAME, TEST_METHOD.name()) + .selectors(selectClass(TestCase.class)) + .execute() + .testEvents() + .assertStatistics(stats -> stats.started(7).succeeded(7).failed(0)); + } + + + @SpringJUnitConfig(Config1.class) + @ActiveProfiles("1") + @NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default + static class TestCase { + + @Autowired + List strings; + + + @Test + void test() { + assertThat(this.strings).containsExactlyInAnyOrder("X", "A1"); + } + + + @Nested + @NestedTestConfiguration(INHERIT) + class InheritedConfigTestCase { + + @Autowired + List localStrings; + + + @Test + void test() { + assertThat(strings) + .isEqualTo(this.localStrings) + .containsExactlyInAnyOrder("X", "A1"); + } + } + + @Nested + @SpringJUnitConfig(Config2.class) + @ActiveProfiles("2") + class ConfigOverriddenByDefaultTestCase { + + @Autowired + List localStrings; + + + @Test + void test() { + assertThat(strings) + .isEqualTo(this.localStrings) + .containsExactlyInAnyOrder("Y", "A2"); + } + } + + @Nested + @NestedTestConfiguration(INHERIT) + @ContextConfiguration(classes = Config2.class) + @ActiveProfiles("2") + class InheritedAndExtendedConfigTestCase { + + @Autowired + List localStrings; + + + @Test + void test() { + assertThat(strings) + .isEqualTo(this.localStrings) + .containsExactlyInAnyOrder("X", "A1", "Y", "A2"); + } + + + @Nested + @NestedTestConfiguration(OVERRIDE) + @SpringJUnitConfig({ Config1.class, Config2.class, Config3.class }) + @ActiveProfiles("3") + class DoubleNestedWithOverriddenConfigTestCase { + + @Autowired + List localStrings; + + + @Test + void test() { + assertThat(strings) + .isEqualTo(this.localStrings) + .containsExactlyInAnyOrder("X", "Y", "Z", "A3"); + } + + + @Nested + @NestedTestConfiguration(INHERIT) + @ActiveProfiles(profiles = "2", inheritProfiles = false) + class TripleNestedWithInheritedConfigButOverriddenProfilesTestCase { + + @Autowired + List localStrings; + + + @Test + void test() { + assertThat(strings) + .isEqualTo(this.localStrings) + .containsExactlyInAnyOrder("X", "Y", "Z", "A2"); + } + } + + @Nested + @NestedTestConfiguration(INHERIT) + class TripleNestedWithInheritedConfigAndTestInterfaceTestCase implements TestInterface { + + @Autowired + List localStrings; + + + @Test + void test() { + assertThat(strings) + .isEqualTo(this.localStrings) + .containsExactlyInAnyOrder("X", "Y", "Z", "A2", "A3"); + } + } + } + } + } + + // ------------------------------------------------------------------------- + + @Configuration + static class Config1 { + + @Bean + String x() { + return "X"; + } + + @Bean + @Profile("1") + String a1() { + return "A1"; + } + } + + @Configuration + static class Config2 { + + @Bean + String y() { + return "Y"; + } + + @Bean + @Profile("2") + String a2() { + return "A2"; + } + } + + @Configuration + static class Config3 { + + @Bean + String z() { + return "Z"; + } + + @Bean + @Profile("3") + String a3() { + return "A3"; + } + } + + @ActiveProfiles("2") + interface TestInterface { + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ConstructorInjectionNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ConstructorInjectionTestClassScopedExtensionContextNestedTests.java similarity index 86% rename from spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ConstructorInjectionNestedTests.java rename to spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ConstructorInjectionTestClassScopedExtensionContextNestedTests.java index d60143d5793..81cf6117107 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ConstructorInjectionNestedTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ConstructorInjectionTestClassScopedExtensionContextNestedTests.java @@ -19,6 +19,7 @@ package org.springframework.test.context.junit.jupiter.nested; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -28,28 +29,29 @@ import org.springframework.context.annotation.Configuration; import org.springframework.test.context.NestedTestConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; -import org.springframework.test.context.junit.jupiter.nested.ConstructorInjectionNestedTests.TopLevelConfig; +import org.springframework.test.context.junit.jupiter.nested.ConstructorInjectionTestClassScopedExtensionContextNestedTests.TopLevelConfig; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE; /** * Integration tests that verify support for {@code @Nested} test classes in conjunction - * with the {@link SpringExtension} in a JUnit Jupiter environment ... when using - * constructor injection as opposed to field injection (see SPR-16653). + * with the {@link SpringExtension} in a JUnit Jupiter environment with test class + * {@link ExtensionContextScope} ... when using constructor injection as opposed + * to field injection (see SPR-16653). * * @author Sam Brannen * @since 5.0.5 - * @see ContextConfigurationNestedTests + * @see ContextConfigurationTestClassScopedExtensionContextNestedTests * @see org.springframework.test.context.junit4.nested.NestedTestsWithSpringRulesTests */ @SpringJUnitConfig(TopLevelConfig.class) @NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default -class ConstructorInjectionNestedTests { +class ConstructorInjectionTestClassScopedExtensionContextNestedTests { final String foo; - ConstructorInjectionNestedTests(TestInfo testInfo, @Autowired String foo) { + ConstructorInjectionTestClassScopedExtensionContextNestedTests(TestInfo testInfo, @Autowired String foo) { this.foo = foo; } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ConstructorInjectionTestMethodScopedExtensionContextNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ConstructorInjectionTestMethodScopedExtensionContextNestedTests.java new file mode 100644 index 00000000000..b6ba04cd91a --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ConstructorInjectionTestMethodScopedExtensionContextNestedTests.java @@ -0,0 +1,173 @@ +/* + * 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.nested; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; +import org.junit.platform.testkit.engine.EngineTestKit; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.NestedTestConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME; +import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE; + +/** + * Integration tests that verify support for {@code @Nested} test classes in conjunction + * with the {@link SpringExtension} in a JUnit Jupiter environment with + * {@link ExtensionContextScope#TEST_METHOD} ... when using constructor injection + * as opposed to field injection (see SPR-16653). + * + * @author Sam Brannen + * @since 6.2.13 + * @see ContextConfigurationTestClassScopedExtensionContextNestedTests + * @see org.springframework.test.context.junit4.nested.NestedTestsWithSpringRulesTests + */ +class ConstructorInjectionTestMethodScopedExtensionContextNestedTests { + + @Test + void runTests() { + EngineTestKit.engine("junit-jupiter") + .configurationParameter(DEFAULT_SCOPE_PROPERTY_NAME, TEST_METHOD.name()) + .selectors(selectClass(TestCase.class)) + .execute() + .testEvents() + .assertStatistics(stats -> stats.started(5).succeeded(5).failed(0)); + } + + + @SpringJUnitConfig(TopLevelConfig.class) + @NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default + static class TestCase { + + final String foo; + + TestCase(TestInfo testInfo, @Autowired String foo) { + this.foo = foo; + } + + @Test + void topLevelTest() { + assertThat(foo).isEqualTo("foo"); + } + + @Nested + @SpringJUnitConfig(NestedConfig.class) + class AutowiredConstructorTestCase { + + final String bar; + + @Autowired + AutowiredConstructorTestCase(String bar) { + this.bar = bar; + } + + @Test + void nestedTest() { + assertThat(foo).isEqualTo("bar"); + assertThat(bar).isEqualTo("bar"); + } + } + + @Nested + @SpringJUnitConfig(NestedConfig.class) + class AutowiredConstructorParameterTestCase { + + final String bar; + + AutowiredConstructorParameterTestCase(@Autowired String bar) { + this.bar = bar; + } + + @Test + void nestedTest() { + assertThat(foo).isEqualTo("bar"); + assertThat(bar).isEqualTo("bar"); + } + } + + @Nested + @SpringJUnitConfig(NestedConfig.class) + class QualifiedConstructorParameterTestCase { + + final String bar; + + QualifiedConstructorParameterTestCase(TestInfo testInfo, @Qualifier("bar") String s) { + this.bar = s; + } + + @Test + void nestedTest() { + assertThat(foo).isEqualTo("bar"); + assertThat(bar).isEqualTo("bar"); + } + } + + @Nested + @SpringJUnitConfig(NestedConfig.class) + class SpelConstructorParameterTestCase { + + final String bar; + final int answer; + + SpelConstructorParameterTestCase(@Autowired String bar, TestInfo testInfo, @Value("#{ 6 * 7 }") int answer) { + this.bar = bar; + this.answer = answer; + } + + @Test + void nestedTest() { + assertThat(foo).isEqualTo("bar"); + assertThat(bar).isEqualTo("bar"); + assertThat(answer).isEqualTo(42); + } + } + + } + + // ------------------------------------------------------------------------- + + @Configuration + static class TopLevelConfig { + + @Bean + String foo() { + return "foo"; + } + } + + @Configuration + static class NestedConfig { + + @Bean + String bar() { + return "bar"; + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextConfigurationNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextConfigurationTestClassScopedExtensionContextNestedTests.java similarity index 92% rename from spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextConfigurationNestedTests.java rename to spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextConfigurationTestClassScopedExtensionContextNestedTests.java index 61022e39a4b..6ee181fe100 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextConfigurationNestedTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextConfigurationTestClassScopedExtensionContextNestedTests.java @@ -18,6 +18,7 @@ package org.springframework.test.context.junit.jupiter.nested; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -27,7 +28,7 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.NestedTestConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; -import org.springframework.test.context.junit.jupiter.nested.ContextConfigurationNestedTests.TopLevelConfig; +import org.springframework.test.context.junit.jupiter.nested.ContextConfigurationTestClassScopedExtensionContextNestedTests.TopLevelConfig; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT; @@ -36,16 +37,17 @@ import static org.springframework.test.context.NestedTestConfiguration.Enclosing /** * Integration tests that verify support for {@code @Nested} test classes using * {@link ContextConfiguration @ContextConfiguration} in conjunction with the - * {@link SpringExtension} in a JUnit Jupiter environment. + * {@link SpringExtension} in a JUnit Jupiter environment with test class + * {@link ExtensionContextScope}. * * @author Sam Brannen * @since 5.0 - * @see ConstructorInjectionNestedTests + * @see ConstructorInjectionTestClassScopedExtensionContextNestedTests * @see org.springframework.test.context.junit4.nested.NestedTestsWithSpringRulesTests */ @SpringJUnitConfig(TopLevelConfig.class) @NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default -class ContextConfigurationNestedTests { +class ContextConfigurationTestClassScopedExtensionContextNestedTests { private static final String FOO = "foo"; private static final String BAR = "bar"; diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextConfigurationTestMethodScopedExtensionContextNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextConfigurationTestMethodScopedExtensionContextNestedTests.java new file mode 100644 index 00000000000..fecf82498ab --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextConfigurationTestMethodScopedExtensionContextNestedTests.java @@ -0,0 +1,234 @@ +/* + * 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.nested; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; +import org.junit.platform.testkit.engine.EngineTestKit; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.NestedTestConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME; +import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE; + +/** + * Integration tests that verify support for {@code @Nested} test classes using + * {@link ContextConfiguration @ContextConfiguration} in conjunction with the + * {@link SpringExtension} in a JUnit Jupiter environment with + * {@link ExtensionContextScope#TEST_METHOD}. + * + * @author Sam Brannen + * @since 6.2.13 + * @see ConstructorInjectionTestClassScopedExtensionContextNestedTests + * @see org.springframework.test.context.junit4.nested.NestedTestsWithSpringRulesTests + */ +class ContextConfigurationTestMethodScopedExtensionContextNestedTests { + + private static final String FOO = "foo"; + private static final String BAR = "bar"; + private static final String BAZ = "baz"; + + + @Test + void runTests() { + EngineTestKit.engine("junit-jupiter") + .configurationParameter(DEFAULT_SCOPE_PROPERTY_NAME, TEST_METHOD.name()) + .selectors(selectClass(TestCase.class)) + .execute() + .testEvents() + .assertStatistics(stats -> stats.started(6).succeeded(6).failed(0)); + } + + + @SpringJUnitConfig(TopLevelConfig.class) + @NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default + static class TestCase { + + private static final String FOO = "foo"; + private static final String BAR = "bar"; + private static final String BAZ = "baz"; + + @Autowired(required = false) + @Qualifier("foo") + String foo; + + + @Test + void topLevelTest() { + assertThat(foo).isEqualTo(FOO); + } + + + @Nested + @SpringJUnitConfig(NestedConfig.class) + class NestedTestCase { + + @Autowired(required = false) + @Qualifier("foo") + String localFoo; + + @Autowired + String bar; + + + @Test + void test() { + assertThat(foo).as("foo bean should not be present").isNull(); + assertThat(this.localFoo).as("local foo bean should not be present").isNull(); + assertThat(this.bar).isEqualTo(BAR); + } + } + + @Nested + @NestedTestConfiguration(INHERIT) + class NestedTestCaseWithInheritedConfigTestCase { + + @Autowired(required = false) + @Qualifier("foo") + String localFoo; + + @Autowired + String bar; + + + @Test + void test() { + // Since the configuration is inherited, the foo field in the outer instance + // and the bar field in the inner instance should both have been injected + // from the test ApplicationContext for the outer instance. + assertThat(foo).isEqualTo(FOO); + assertThat(this.localFoo).isEqualTo(FOO); + assertThat(this.bar).isEqualTo(FOO); + } + + + @Nested + @NestedTestConfiguration(OVERRIDE) + @SpringJUnitConfig(NestedConfig.class) + class DoubleNestedWithOverriddenConfigTestCase { + + @Autowired(required = false) + @Qualifier("foo") + String localFoo; + + @Autowired + String bar; + + + @Test + void test() { + assertThat(foo).as("foo bean should not be present").isNull(); + assertThat(this.localFoo).as("local foo bean should not be present").isNull(); + assertThat(this.bar).isEqualTo(BAR); + } + + + @Nested + @NestedTestConfiguration(INHERIT) + class TripleNestedWithInheritedConfigTestCase { + + @Autowired(required = false) + @Qualifier("foo") + String localFoo; + + @Autowired + String bar; + + + @Test + void test() { + assertThat(foo).as("foo bean should not be present").isNull(); + assertThat(this.localFoo).as("local foo bean should not be present").isNull(); + assertThat(this.bar).isEqualTo(BAR); + } + } + + @Nested + @NestedTestConfiguration(INHERIT) + class TripleNestedWithInheritedConfigAndTestInterfaceTestCase implements TestInterface { + + @Autowired(required = false) + @Qualifier("foo") + String localFoo; + + @Autowired + @Qualifier("bar") + String bar; + + @Autowired + String baz; + + + @Test + void test() { + assertThat(foo).as("foo bean should not be present").isNull(); + assertThat(this.localFoo).as("local foo bean should not be present").isNull(); + assertThat(this.bar).isEqualTo(BAR); + assertThat(this.baz).isEqualTo(BAZ); + } + } + } + } + + } + + // ------------------------------------------------------------------------- + + @Configuration + static class TopLevelConfig { + + @Bean + String foo() { + return FOO; + } + } + + @Configuration + static class NestedConfig { + + @Bean + String bar() { + return BAR; + } + } + + @Configuration + static class TestInterfaceConfig { + + @Bean + String baz() { + return BAZ; + } + } + + @ContextConfiguration(classes = TestInterfaceConfig.class) + interface TestInterface { + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextHierarchyNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextHierarchyTestClassScopedExtensionContextNestedTests.java similarity index 92% rename from spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextHierarchyNestedTests.java rename to spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextHierarchyTestClassScopedExtensionContextNestedTests.java index a8608aa7ee5..c47cf2a630b 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextHierarchyNestedTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextHierarchyTestClassScopedExtensionContextNestedTests.java @@ -19,6 +19,7 @@ package org.springframework.test.context.junit.jupiter.nested; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -30,7 +31,7 @@ import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.NestedTestConfiguration; import org.springframework.test.context.aot.DisabledInAotMode; import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.context.junit.jupiter.nested.ContextHierarchyNestedTests.ParentConfig; +import org.springframework.test.context.junit.jupiter.nested.ContextHierarchyTestClassScopedExtensionContextNestedTests.ParentConfig; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT; @@ -39,7 +40,8 @@ import static org.springframework.test.context.NestedTestConfiguration.Enclosing /** * Integration tests that verify support for {@code @Nested} test classes using * {@link ContextHierarchy @ContextHierarchy} in conjunction with the - * {@link SpringExtension} in a JUnit Jupiter environment. + * {@link SpringExtension} in a JUnit Jupiter environment with test class + * {@link ExtensionContextScope}. * * @author Sam Brannen * @since 5.3 @@ -48,7 +50,7 @@ import static org.springframework.test.context.NestedTestConfiguration.Enclosing @ContextHierarchy(@ContextConfiguration(classes = ParentConfig.class)) @NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default @DisabledInAotMode("@ContextHierarchy is not supported in AOT") -class ContextHierarchyNestedTests { +class ContextHierarchyTestClassScopedExtensionContextNestedTests { private static final String FOO = "foo"; private static final String BAR = "bar"; @@ -97,7 +99,7 @@ class ContextHierarchyNestedTests { @Nested @NestedTestConfiguration(INHERIT) @ContextConfiguration(classes = Child1Config.class) - class NestedTestCaseWithInheritedConfigTests { + class NestedInheritedCfgTests { @Autowired String bar; @@ -125,7 +127,7 @@ class ContextHierarchyNestedTests { @ContextConfiguration(classes = ParentConfig.class), @ContextConfiguration(classes = Child2Config.class) }) - class DoubleNestedTestCaseWithOverriddenConfigTests { + class DoubleNestedOverriddenCfgTests { @Autowired String bar; @@ -146,7 +148,7 @@ class ContextHierarchyNestedTests { @Nested @NestedTestConfiguration(INHERIT) - class TripleNestedWithInheritedConfigAndTestInterfaceTests implements TestInterface { + class TripleNestedInheritedCfgAndTestInterfaceTests implements TestInterface { @Autowired @Qualifier("foo") diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextHierarchyTestMethodScopedExtensionContextNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextHierarchyTestMethodScopedExtensionContextNestedTests.java new file mode 100644 index 00000000000..dfc0777c33d --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/ContextHierarchyTestMethodScopedExtensionContextNestedTests.java @@ -0,0 +1,261 @@ +/* + * 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.nested; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; +import org.junit.platform.testkit.engine.EngineTestKit; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.ContextHierarchy; +import org.springframework.test.context.NestedTestConfiguration; +import org.springframework.test.context.aot.DisabledInAotMode; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME; +import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE; + +/** + * Integration tests that verify support for {@code @Nested} test classes using + * {@link ContextHierarchy @ContextHierarchy} in conjunction with the + * {@link SpringExtension} in a JUnit Jupiter environment with + * {@link ExtensionContextScope#TEST_METHOD}. + * + * @author Sam Brannen + * @since 6.2.13 + */ +class ContextHierarchyTestMethodScopedExtensionContextNestedTests { + + private static final String FOO = "foo"; + private static final String BAR = "bar"; + private static final String BAZ = "baz"; + private static final String QUX = "qux"; + + + @Test + void runTests() { + EngineTestKit.engine("junit-jupiter") + .configurationParameter(DEFAULT_SCOPE_PROPERTY_NAME, TEST_METHOD.name()) + .selectors(selectClass(TestCase.class)) + .execute() + .testEvents() + .assertStatistics(stats -> stats.started(5).succeeded(5).failed(0)); + } + + + @ExtendWith(SpringExtension.class) + @ContextHierarchy(@ContextConfiguration(classes = ParentConfig.class)) + @NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default + @DisabledInAotMode("@ContextHierarchy is not supported in AOT") + static class TestCase { + + @Autowired + String foo; + + @Autowired + ApplicationContext context; + + + @Test + void topLevelTest() { + assertThat(this.context).as("local ApplicationContext").isNotNull(); + assertThat(this.context.getParent()).as("parent ApplicationContext").isNull(); + + assertThat(foo).isEqualTo(FOO); + } + + @Nested + @ContextConfiguration(classes = NestedConfig.class) + class NestedTestCase { + + @Autowired + String bar; + + @Autowired + ApplicationContext context; + + + @Test + void nestedTest() { + assertThat(this.context).as("local ApplicationContext").isNotNull(); + assertThat(this.context.getParent()).as("parent ApplicationContext").isNull(); + + // The foo field in the outer instance should have been injected from + // the test ApplicationContext for NestedTestCase. + assertThat(foo).isEqualTo(BAR); + assertThat(this.bar).isEqualTo(BAR); + } + } + + @Nested + @NestedTestConfiguration(INHERIT) + @ContextConfiguration(classes = Child1Config.class) + class NestedInheritedCfgTestCase { + + @Autowired + String bar; + + @Autowired + ApplicationContext context; + + + @Test + void nestedTest() { + assertThat(this.context).as("local ApplicationContext").isNotNull(); + assertThat(this.context.getParent()).as("parent ApplicationContext").isNotNull(); + + // The foo field in the outer instance and the bar field in the inner + // instance should both have been injected from the test ApplicationContext + // for the inner instance. + assertThat(foo).as("foo") + .isEqualTo(this.context.getBean("foo", String.class)) + .isEqualTo(QUX + 1); + assertThat(this.bar).isEqualTo(BAZ + 1); + } + + @Nested + @NestedTestConfiguration(OVERRIDE) + @ContextHierarchy({ + @ContextConfiguration(classes = ParentConfig.class), + @ContextConfiguration(classes = Child2Config.class) + }) + class DoubleNestedOverriddenCfgTestCase { + + @Autowired + String bar; + + @Autowired + ApplicationContext context; + + + @Test + void nestedTest() { + assertThat(this.context).as("local ApplicationContext").isNotNull(); + assertThat(this.context.getParent()).as("parent ApplicationContext").isNotNull(); + + assertThat(foo).as("foo") + .isEqualTo(this.context.getBean("foo", String.class)) + .isEqualTo(QUX + 2); + assertThat(this.bar).isEqualTo(BAZ + 2); + } + + @Nested + @NestedTestConfiguration(INHERIT) + class TripleNestedInheritedCfgAndTestInterfaceTestCase implements TestInterface { + + @Autowired + @Qualifier("foo") + String localFoo; + + @Autowired + String bar; + + @Autowired + ApplicationContext context; + + + @Test + void nestedTest() { + assertThat(this.context).as("local ApplicationContext").isNotNull(); + assertThat(this.context.getParent()).as("parent ApplicationContext").isNotNull(); + assertThat(this.context.getParent().getParent()).as("grandparent ApplicationContext").isNotNull(); + + assertThat(foo).as("foo") + .isEqualTo(this.localFoo) + .isEqualTo(this.context.getBean("foo", String.class)) + .isEqualTo("test interface"); + assertThat(this.bar).isEqualTo(BAZ + 2); + } + } + } + } + + } + + // ------------------------------------------------------------------------- + + @Configuration + static class ParentConfig { + + @Bean + String foo() { + return FOO; + } + } + + @Configuration + static class Child1Config { + + @Bean + String foo() { + return QUX + 1; + } + + @Bean + String bar() { + return BAZ + 1; + } + } + + @Configuration + static class Child2Config { + + @Bean + String foo() { + return QUX + 2; + } + + @Bean + String bar() { + return BAZ + 2; + } + } + + @Configuration + static class NestedConfig { + + @Bean + String bar() { + return BAR; + } + } + + @Configuration + static class TestInterfaceConfig { + + @Bean + String foo() { + return "test interface"; + } + } + + @ContextConfiguration(classes = TestInterfaceConfig.class) + interface TestInterface { + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestConstructorNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestConstructorTestClassScopedExtensionContextNestedTests.java similarity index 92% rename from spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestConstructorNestedTests.java rename to spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestConstructorTestClassScopedExtensionContextNestedTests.java index 3cda1777d78..6d2da9c7a4f 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestConstructorNestedTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestConstructorTestClassScopedExtensionContextNestedTests.java @@ -18,6 +18,7 @@ package org.springframework.test.context.junit.jupiter.nested; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; @@ -36,7 +37,8 @@ import static org.springframework.test.context.TestConstructor.AutowireMode.ANNO /** * Integration tests that verify support for {@code @Nested} test classes using * {@link TestConstructor @TestConstructor} in conjunction with the - * {@link SpringExtension} in a JUnit Jupiter environment. + * {@link SpringExtension} in a JUnit Jupiter environment with test class + * {@link ExtensionContextScope}. * * @author Sam Brannen * @since 5.3 @@ -44,9 +46,9 @@ import static org.springframework.test.context.TestConstructor.AutowireMode.ANNO @SpringJUnitConfig @TestConstructor(autowireMode = ALL) @NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default -class TestConstructorNestedTests { +class TestConstructorTestClassScopedExtensionContextNestedTests { - TestConstructorNestedTests(String text) { + TestConstructorTestClassScopedExtensionContextNestedTests(String text) { assertThat(text).isEqualTo("enigma"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestConstructorTestMethodScopedExtensionContextNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestConstructorTestMethodScopedExtensionContextNestedTests.java new file mode 100644 index 00000000000..21d423155f2 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestConstructorTestMethodScopedExtensionContextNestedTests.java @@ -0,0 +1,190 @@ +/* + * 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.nested; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; +import org.junit.platform.testkit.engine.EngineTestKit; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.NestedTestConfiguration; +import org.springframework.test.context.TestConstructor; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME; +import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE; +import static org.springframework.test.context.TestConstructor.AutowireMode.ALL; +import static org.springframework.test.context.TestConstructor.AutowireMode.ANNOTATED; + +/** + * Integration tests that verify support for {@code @Nested} test classes using + * {@link TestConstructor @TestConstructor} in conjunction with the + * {@link SpringExtension} in a JUnit Jupiter environment with + * {@link ExtensionContextScope#TEST_METHOD}. + * + * @author Sam Brannen + * @since 6.2.13 + */ +class TestConstructorTestMethodScopedExtensionContextNestedTests { + + @Test + void runTests() { + EngineTestKit.engine("junit-jupiter") + .configurationParameter(DEFAULT_SCOPE_PROPERTY_NAME, TEST_METHOD.name()) + .selectors(selectClass(TestCase.class)) + .execute() + .testEvents() + .assertStatistics(stats -> stats.started(8).succeeded(8).failed(0)); + } + + + @SpringJUnitConfig(Config.class) + @TestConstructor(autowireMode = ALL) + @NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default + static class TestCase { + + TestCase(String text) { + assertThat(text).isEqualTo("enigma"); + } + + @Test + void test() { + } + + + @Nested + @SpringJUnitConfig(Config.class) + @TestConstructor(autowireMode = ANNOTATED) + class ConfigOverriddenByDefaultTestCase { + + @Autowired + ConfigOverriddenByDefaultTestCase(String text) { + assertThat(text).isEqualTo("enigma"); + } + + @Test + void test() { + } + } + + @Nested + @NestedTestConfiguration(INHERIT) + class InheritedConfigTestCase { + + InheritedConfigTestCase(String text) { + assertThat(text).isEqualTo("enigma"); + } + + @Test + void test() { + } + + + @Nested + class DoubleNestedWithImplicitlyInheritedConfigTestCase { + + DoubleNestedWithImplicitlyInheritedConfigTestCase(String text) { + assertThat(text).isEqualTo("enigma"); + } + + @Test + void test() { + } + + + @Nested + class TripleNestedWithImplicitlyInheritedConfigTestCase { + + TripleNestedWithImplicitlyInheritedConfigTestCase(String text) { + assertThat(text).isEqualTo("enigma"); + } + + @Test + void test() { + } + } + } + + @Nested + @NestedTestConfiguration(OVERRIDE) + @SpringJUnitConfig(Config.class) + @TestConstructor(autowireMode = ANNOTATED) + class DoubleNestedWithOverriddenConfigTestCase { + + DoubleNestedWithOverriddenConfigTestCase(@Autowired String text) { + assertThat(text).isEqualTo("enigma"); + } + + @Test + void test() { + } + + + @Nested + @NestedTestConfiguration(INHERIT) + class TripleNestedWithInheritedConfigTestCase { + + @Autowired + TripleNestedWithInheritedConfigTestCase(String text) { + assertThat(text).isEqualTo("enigma"); + } + + @Test + void test() { + } + } + + @Nested + @NestedTestConfiguration(INHERIT) + class TripleNestedWithInheritedConfigAndTestInterfaceTestCase implements TestInterface { + + TripleNestedWithInheritedConfigAndTestInterfaceTestCase(String text) { + assertThat(text).isEqualTo("enigma"); + } + + @Test + void test() { + } + } + } + } + } + + // ------------------------------------------------------------------------- + + @Configuration + static class Config { + + @Bean + String text() { + return "enigma"; + } + } + + @TestConstructor(autowireMode = ALL) + interface TestInterface { + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestPropertySourceNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestPropertySourceTestClassScopedExtensionContextNestedTests.java similarity index 90% rename from spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestPropertySourceNestedTests.java rename to spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestPropertySourceTestClassScopedExtensionContextNestedTests.java index 49e5e282886..369d1734175 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestPropertySourceNestedTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestPropertySourceTestClassScopedExtensionContextNestedTests.java @@ -18,6 +18,7 @@ package org.springframework.test.context.junit.jupiter.nested; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; @@ -26,7 +27,7 @@ import org.springframework.test.context.NestedTestConfiguration; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; -import org.springframework.test.context.junit.jupiter.nested.TestPropertySourceNestedTests.Config; +import org.springframework.test.context.junit.jupiter.nested.TestPropertySourceTestClassScopedExtensionContextNestedTests.Config; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT; @@ -35,7 +36,8 @@ import static org.springframework.test.context.NestedTestConfiguration.Enclosing /** * Integration tests that verify support for {@code @Nested} test classes using * {@link TestPropertySource @TestPropertySource} in conjunction with the - * {@link SpringExtension} in a JUnit Jupiter environment. + * {@link SpringExtension} in a JUnit Jupiter environment with test class + * {@link ExtensionContextScope}. * * @author Sam Brannen * @since 5.3 @@ -43,7 +45,7 @@ import static org.springframework.test.context.NestedTestConfiguration.Enclosing @SpringJUnitConfig(Config.class) @TestPropertySource(properties = "p1 = v1") @NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default -class TestPropertySourceNestedTests { +class TestPropertySourceTestClassScopedExtensionContextNestedTests { @Autowired Environment env1; @@ -57,7 +59,7 @@ class TestPropertySourceNestedTests { @Nested @NestedTestConfiguration(INHERIT) - class InheritedConfigTests { + class InheritedCfgTests { @Autowired Environment env2; @@ -93,7 +95,7 @@ class TestPropertySourceNestedTests { @NestedTestConfiguration(INHERIT) @TestPropertySource(properties = "p2a = v2a") @TestPropertySource(properties = "p2b = v2b") - class InheritedAndExtendedConfigTests { + class InheritedAndExtendedCfgTests { @Autowired Environment env2; @@ -113,7 +115,7 @@ class TestPropertySourceNestedTests { @NestedTestConfiguration(OVERRIDE) @SpringJUnitConfig(Config.class) @TestPropertySource(properties = "p3 = v3") - class L3WithOverriddenConfigTests { + class L3OverriddenCfgTests { @Autowired Environment env3; @@ -136,7 +138,7 @@ class TestPropertySourceNestedTests { @Nested @NestedTestConfiguration(INHERIT) @TestPropertySource(properties = {"p3 = v34", "p4 = v4"}, inheritProperties = false) - class L4WithInheritedConfigButOverriddenTestPropertiesTests { + class L4InheritedCfgButOverriddenTestPropsTests { @Autowired Environment env4; @@ -161,7 +163,7 @@ class TestPropertySourceNestedTests { } @Nested - class L5WithInheritedConfigAndTestInterfaceTests implements TestInterface { + class L5InheritedCfgAndTestInterfaceTests implements TestInterface { @Autowired Environment env5; diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestPropertySourceTestMethodScopedExtensionContextNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestPropertySourceTestMethodScopedExtensionContextNestedTests.java new file mode 100644 index 00000000000..9be4d3db645 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/TestPropertySourceTestMethodScopedExtensionContextNestedTests.java @@ -0,0 +1,198 @@ +/* + * 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.nested; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; +import org.junit.platform.testkit.engine.EngineTestKit; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +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; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME; +import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.INHERIT; +import static org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration.OVERRIDE; + +/** + * Integration tests that verify support for {@code @Nested} test classes using + * {@link TestPropertySource @TestPropertySource} in conjunction with the + * {@link SpringExtension} in a JUnit Jupiter environment with + * {@link ExtensionContextScope#TEST_METHOD}. + * + * @author Sam Brannen + * @since 6.2.13 + */ +class TestPropertySourceTestMethodScopedExtensionContextNestedTests { + + @Test + void runTests() { + EngineTestKit.engine("junit-jupiter") + .configurationParameter(DEFAULT_SCOPE_PROPERTY_NAME, TEST_METHOD.name()) + .selectors(selectClass(TestCase.class)) + .execute() + .testEvents() + .assertStatistics(stats -> stats.started(7).succeeded(7).failed(0)); + } + + + @SpringJUnitConfig(Config.class) + @TestPropertySource(properties = "p1 = v1") + @NestedTestConfiguration(OVERRIDE) // since INHERIT is now the global default + static class TestCase { + + @Autowired + Environment env1; + + + @Test + void propertiesInEnvironment() { + assertThat(env1.getProperty("p1")).isEqualTo("v1"); + } + + + @Nested + @NestedTestConfiguration(INHERIT) + class InheritedCfgTestCase { + + @Autowired + Environment env2; + + + @Test + void propertiesInEnvironment() { + assertThat(env1).isSameAs(env2); + assertThat(env2.getProperty("p1")).isEqualTo("v1"); + } + } + + @Nested + @SpringJUnitConfig(Config.class) + @TestPropertySource(properties = "p2 = v2") + class ConfigOverriddenByDefaultTestCase { + + @Autowired + Environment env2; + + + @Test + void propertiesInEnvironment() { + assertThat(env1).isSameAs(env2); + assertThat(env2.getProperty("p1")).isNull(); + assertThat(env2.getProperty("p2")).isEqualTo("v2"); + } + } + + @Nested + @NestedTestConfiguration(INHERIT) + @TestPropertySource(properties = "p2a = v2a") + @TestPropertySource(properties = "p2b = v2b") + class InheritedAndExtendedCfgTestCase { + + @Autowired + Environment env2; + + + @Test + void propertiesInEnvironment() { + assertThat(env1).isSameAs(env2); + assertThat(env2.getProperty("p1")).isEqualTo("v1"); + assertThat(env2.getProperty("p2a")).isEqualTo("v2a"); + assertThat(env2.getProperty("p2b")).isEqualTo("v2b"); + } + + + @Nested + @NestedTestConfiguration(OVERRIDE) + @SpringJUnitConfig(Config.class) + @TestPropertySource(properties = "p3 = v3") + class L3OverriddenCfgTestCase { + + @Autowired + Environment env3; + + + @Test + void propertiesInEnvironment() { + assertThat(env1).isSameAs(env2).isSameAs(env3); + assertThat(env3.getProperty("p1")).isNull(); + assertThat(env3.getProperty("p2")).isNull(); + assertThat(env3.getProperty("p3")).isEqualTo("v3"); + } + + + @Nested + @NestedTestConfiguration(INHERIT) + @TestPropertySource(properties = {"p3 = v34", "p4 = v4"}, inheritProperties = false) + class L4InheritedCfgButOverriddenTestPropertiesTestCase { + + @Autowired + Environment env4; + + + @Test + void propertiesInEnvironment() { + assertThat(env1).isSameAs(env2).isSameAs(env3).isSameAs(env4); + assertThat(env4.getProperty("p1")).isNull(); + assertThat(env4.getProperty("p2")).isNull(); + assertThat(env4.getProperty("p3")).isEqualTo("v34"); + assertThat(env4.getProperty("p4")).isEqualTo("v4"); + } + + @Nested + class L5InheritedCfgAndTestInterfaceTestCase implements TestInterface { + + @Autowired + Environment env5; + + + @Test + void propertiesInEnvironment() { + assertThat(env4).isSameAs(env5); + assertThat(env5.getProperty("p1")).isNull(); + assertThat(env5.getProperty("p2")).isNull(); + assertThat(env5.getProperty("p3")).isEqualTo("v34"); + assertThat(env5.getProperty("p4")).isEqualTo("v4"); + assertThat(env5.getProperty("foo")).isEqualTo("bar"); + assertThat(env5.getProperty("enigma")).isEqualTo("42"); + } + } + } + } + } + } + + // ------------------------------------------------------------------------- + + @Configuration + static class Config { + /* no user beans required for these tests */ + } + + @TestPropertySource(properties = { "foo = bar", "enigma: 42" }) + interface TestInterface { + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/WebAppConfigurationNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/WebAppConfigurationNestedTests.java index c2a472a6b8c..c8ab547ee98 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/WebAppConfigurationNestedTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/WebAppConfigurationNestedTests.java @@ -40,7 +40,8 @@ import static org.springframework.test.context.NestedTestConfiguration.Enclosing * * @author Sam Brannen * @since 5.0 - * @see ConstructorInjectionNestedTests + * @see ConstructorInjectionTestClassScopedExtensionContextNestedTests + * @see ConstructorInjectionTestMethodScopedExtensionContextNestedTests * @see org.springframework.test.context.junit4.nested.NestedTestsWithSpringRulesTests */ @SpringJUnitWebConfig(Config.class) diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/nested/NestedTestsWithSpringRulesTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/nested/NestedTestsWithSpringRulesTests.java index dbb3e2d40e9..a888148a585 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/nested/NestedTestsWithSpringRulesTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/nested/NestedTestsWithSpringRulesTests.java @@ -37,7 +37,8 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Sam Brannen * @since 5.0 - * @see org.springframework.test.context.junit.jupiter.nested.ContextConfigurationNestedTests + * @see org.springframework.test.context.junit.jupiter.nested.ContextConfigurationTestClassScopedExtensionContextNestedTests + * @see org.springframework.test.context.junit.jupiter.nested.ContextConfigurationTestMethodScopedExtensionContextNestedTests */ @RunWith(HierarchicalContextRunner.class) @ContextConfiguration(classes = TopLevelConfig.class) From ba70c1384ad692dd05e3986a78f7623ed0e0bd0f Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Wed, 22 Oct 2025 17:07:53 +0200 Subject: [PATCH 2/2] Polish SpringExtension internals --- .../test/context/junit/jupiter/SpringExtension.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java index 4cb366668f8..2aeea63a946 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java @@ -302,18 +302,19 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { Parameter parameter = parameterContext.getParameter(); + Class parameterType = parameter.getType(); Executable executable = parameter.getDeclaringExecutable(); PropertyProvider junitPropertyProvider = propertyName -> extensionContext.getConfigurationParameter(propertyName).orElse(null); return (TestConstructorUtils.isAutowirableConstructor(executable, junitPropertyProvider) || - ApplicationContext.class.isAssignableFrom(parameter.getType()) || - supportsApplicationEvents(parameterContext) || + ApplicationContext.class.isAssignableFrom(parameterType) || + supportsApplicationEvents(parameterType, executable) || ParameterResolutionDelegate.isAutowirable(parameter, parameterContext.getIndex())); } - private boolean supportsApplicationEvents(ParameterContext parameterContext) { - if (ApplicationEvents.class.isAssignableFrom(parameterContext.getParameter().getType())) { - Assert.isTrue(parameterContext.getDeclaringExecutable() instanceof Method, + private boolean supportsApplicationEvents(Class parameterType, Executable executable) { + if (ApplicationEvents.class.isAssignableFrom(parameterType)) { + Assert.isTrue(executable instanceof Method, "ApplicationEvents can only be injected into test and lifecycle methods"); return true; }