Skip to content

Commit d24a31d

Browse files
committed
Support JUnit Jupiter ExtensionContextScope.TEST_METHOD
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 82c34f7 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
1 parent a5141b1 commit d24a31d

File tree

21 files changed

+1859
-43
lines changed

21 files changed

+1859
-43
lines changed

spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideTestExecutionListener.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,17 @@ public void beforeTestMethod(TestContext testContext) throws Exception {
9393
* a corresponding bean override instance.
9494
*/
9595
private static void injectFields(TestContext testContext) {
96-
List<BeanOverrideHandler> handlers = BeanOverrideHandler.forTestClass(testContext.getTestClass());
96+
Object testInstance = testContext.getTestInstance();
97+
// Since JUnit Jupiter 5.12, if the SpringExtension is used with Jupiter's
98+
// ExtensionContextScope.TEST_METHOD mode, the value returned from
99+
// testContext.getTestClass() may refer to the declaring class of the test
100+
// method which is about to be invoked (which may be in a @Nested class
101+
// within the class for the test instance). Thus, we use the class for the
102+
// test instance as the "test class".
103+
Class<?> testClass = testInstance.getClass();
104+
105+
List<BeanOverrideHandler> handlers = BeanOverrideHandler.forTestClass(testClass);
97106
if (!handlers.isEmpty()) {
98-
Object testInstance = testContext.getTestInstance();
99107
ApplicationContext applicationContext = testContext.getApplicationContext();
100108

101109
Assert.state(applicationContext.containsBean(BeanOverrideRegistry.BEAN_NAME), () -> """
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.junit.jupiter.api.DisplayName;
2020
import org.junit.jupiter.api.Nested;
2121
import org.junit.jupiter.api.Test;
22+
import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope;
2223

2324
import org.springframework.context.ApplicationContext;
2425
import org.springframework.context.annotation.Bean;
@@ -28,14 +29,15 @@
2829
import static org.assertj.core.api.Assertions.assertThat;
2930

3031
/**
31-
* Integration tests for {@link TestBean} that use by-name lookup.
32+
* Integration tests for {@link TestBean} that use by-name lookup with test class
33+
* {@link ExtensionContextScope}.
3234
*
3335
* @author Simon Baslé
3436
* @author Sam Brannen
3537
* @since 6.2
3638
*/
3739
@SpringJUnitConfig
38-
public class TestBeanByNameLookupIntegrationTests {
40+
public class TestBeanByNameLookupTestClassScopedExtensionContextIntegrationTests {
3941

4042
@TestBean(name = "field")
4143
String field;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/*
2+
* Copyright 2002-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.context.bean.override.convention;
18+
19+
import org.junit.jupiter.api.DisplayName;
20+
import org.junit.jupiter.api.Nested;
21+
import org.junit.jupiter.api.Test;
22+
import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope;
23+
import org.junit.platform.testkit.engine.EngineTestKit;
24+
25+
import org.springframework.context.ApplicationContext;
26+
import org.springframework.context.annotation.Bean;
27+
import org.springframework.context.annotation.Configuration;
28+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
29+
30+
import static org.assertj.core.api.Assertions.assertThat;
31+
import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME;
32+
import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD;
33+
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
34+
35+
36+
/**
37+
* Integration tests for {@link TestBean} that use by-name lookup with
38+
* {@link ExtensionContextScope#TEST_METHOD}.
39+
*
40+
* @author Simon Baslé
41+
* @author Sam Brannen
42+
* @since 6.2.13
43+
*/
44+
public class TestBeanByNameLookupTestMethodScopedExtensionContextIntegrationTests {
45+
46+
@Test
47+
void runTests() {
48+
EngineTestKit.engine("junit-jupiter")
49+
.configurationParameter(DEFAULT_SCOPE_PROPERTY_NAME, TEST_METHOD.name())
50+
.selectors(selectClass(TestCase.class))
51+
.execute()
52+
.testEvents()
53+
.assertStatistics(stats -> stats.started(12).succeeded(12).failed(0));
54+
}
55+
56+
57+
@SpringJUnitConfig
58+
public static class TestCase {
59+
60+
@TestBean(name = "field")
61+
String field;
62+
63+
@TestBean(name = "methodRenamed1", methodName = "field")
64+
String methodRenamed1;
65+
66+
static String field() {
67+
return "fieldOverride";
68+
}
69+
70+
static String nestedField() {
71+
return "nestedFieldOverride";
72+
}
73+
74+
@Test
75+
void fieldHasOverride(ApplicationContext ctx) {
76+
assertThat(ctx.getBean("field")).as("applicationContext").isEqualTo("fieldOverride");
77+
assertThat(field).as("injection point").isEqualTo("fieldOverride");
78+
}
79+
80+
@Test
81+
void fieldWithMethodNameHasOverride(ApplicationContext ctx) {
82+
assertThat(ctx.getBean("methodRenamed1")).as("applicationContext").isEqualTo("fieldOverride");
83+
assertThat(methodRenamed1).as("injection point").isEqualTo("fieldOverride");
84+
}
85+
86+
87+
@Nested
88+
@DisplayName("With @TestBean in enclosing class and in @Nested class")
89+
public class TestBeanFieldInEnclosingClassTestCase {
90+
91+
@TestBean(name = "nestedField")
92+
String nestedField;
93+
94+
@TestBean(name = "methodRenamed2", methodName = "nestedField")
95+
String methodRenamed2;
96+
97+
98+
@Test
99+
void fieldHasOverride(ApplicationContext ctx) {
100+
assertThat(ctx.getBean("field")).as("applicationContext").isEqualTo("fieldOverride");
101+
assertThat(field).as("injection point").isEqualTo("fieldOverride");
102+
}
103+
104+
@Test
105+
void fieldWithMethodNameHasOverride(ApplicationContext ctx) {
106+
assertThat(ctx.getBean("methodRenamed1")).as("applicationContext").isEqualTo("fieldOverride");
107+
assertThat(methodRenamed1).as("injection point").isEqualTo("fieldOverride");
108+
}
109+
110+
@Test
111+
void nestedFieldHasOverride(ApplicationContext ctx) {
112+
assertThat(ctx.getBean("nestedField")).as("applicationContext").isEqualTo("nestedFieldOverride");
113+
assertThat(nestedField).isEqualTo("nestedFieldOverride");
114+
}
115+
116+
@Test
117+
void nestedFieldWithMethodNameHasOverride(ApplicationContext ctx) {
118+
assertThat(ctx.getBean("methodRenamed2")).as("applicationContext").isEqualTo("nestedFieldOverride");
119+
assertThat(methodRenamed2).isEqualTo("nestedFieldOverride");
120+
}
121+
122+
@Nested
123+
@DisplayName("With @TestBean in the enclosing classes")
124+
public class TestBeanFieldInEnclosingClassLevel2TestCase {
125+
126+
@Test
127+
void fieldHasOverride(ApplicationContext ctx) {
128+
assertThat(ctx.getBean("field")).as("applicationContext").isEqualTo("fieldOverride");
129+
assertThat(field).as("injection point").isEqualTo("fieldOverride");
130+
}
131+
132+
@Test
133+
void fieldWithMethodNameHasOverride(ApplicationContext ctx) {
134+
assertThat(ctx.getBean("methodRenamed1")).as("applicationContext").isEqualTo("fieldOverride");
135+
assertThat(methodRenamed1).as("injection point").isEqualTo("fieldOverride");
136+
}
137+
138+
@Test
139+
void nestedFieldHasOverride(ApplicationContext ctx) {
140+
assertThat(ctx.getBean("nestedField")).as("applicationContext").isEqualTo("nestedFieldOverride");
141+
assertThat(nestedField).isEqualTo("nestedFieldOverride");
142+
}
143+
144+
@Test
145+
void nestedFieldWithMethodNameHasOverride(ApplicationContext ctx) {
146+
assertThat(ctx.getBean("methodRenamed2")).as("applicationContext").isEqualTo("nestedFieldOverride");
147+
assertThat(methodRenamed2).isEqualTo("nestedFieldOverride");
148+
}
149+
}
150+
}
151+
152+
@Nested
153+
@DisplayName("With factory method in enclosing class")
154+
public class TestBeanFactoryMethodInEnclosingClassTestCase {
155+
156+
@TestBean(methodName = "nestedField", name = "nestedField")
157+
String nestedField;
158+
159+
@Test
160+
void nestedFieldHasOverride(ApplicationContext ctx) {
161+
assertThat(ctx.getBean("nestedField")).as("applicationContext").isEqualTo("nestedFieldOverride");
162+
assertThat(nestedField).isEqualTo("nestedFieldOverride");
163+
}
164+
165+
@Nested
166+
@DisplayName("With factory method in the enclosing class of the enclosing class")
167+
public class TestBeanFactoryMethodInEnclosingClassLevel2TestCase {
168+
169+
@TestBean(methodName = "nestedField", name = "nestedNestedField")
170+
String nestedNestedField;
171+
172+
@Test
173+
void nestedFieldHasOverride(ApplicationContext ctx) {
174+
assertThat(ctx.getBean("nestedNestedField")).as("applicationContext").isEqualTo("nestedFieldOverride");
175+
assertThat(nestedNestedField).isEqualTo("nestedFieldOverride");
176+
}
177+
}
178+
}
179+
}
180+
181+
@Configuration(proxyBeanMethods = false)
182+
static class Config {
183+
184+
@Bean("field")
185+
String bean1() {
186+
return "prod";
187+
}
188+
189+
@Bean("nestedField")
190+
String bean2() {
191+
return "nestedProd";
192+
}
193+
194+
@Bean("methodRenamed1")
195+
String bean3() {
196+
return "Prod";
197+
}
198+
199+
@Bean("methodRenamed2")
200+
String bean4() {
201+
return "NestedProd";
202+
}
203+
}
204+
205+
}
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.junit.jupiter.api.DisplayName;
2020
import org.junit.jupiter.api.Nested;
2121
import org.junit.jupiter.api.Test;
22+
import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope;
2223

2324
import org.springframework.beans.factory.annotation.Autowired;
2425
import org.springframework.beans.factory.annotation.Qualifier;
@@ -33,14 +34,15 @@
3334
import static org.assertj.core.api.Assertions.assertThat;
3435

3536
/**
36-
* Integration tests for {@link MockitoBean} that use by-name lookup.
37+
* Integration tests for {@link MockitoBean} that use by-name lookup with test class
38+
* {@link ExtensionContextScope}.
3739
*
3840
* @author Simon Baslé
3941
* @author Sam Brannen
4042
* @since 6.2
4143
*/
4244
@SpringJUnitConfig
43-
public class MockitoBeanByNameLookupIntegrationTests {
45+
public class MockitoBeanByNameLookupTestClassScopedExtensionContextIntegrationTests {
4446

4547
@MockitoBean("field")
4648
ExampleService field;

0 commit comments

Comments
 (0)