Skip to content

Commit cd60a00

Browse files
committed
Reject identical Bean Overrides
Prior to this commit, the Bean Override feature in the Spring TestContext Framework (for annotations such as @⁠MockitoBean and @⁠TestBean) silently allowed one bean override to override another "identical" bean override; however, Spring Boot's @⁠MockBean and @⁠SpyBean support preemptively rejects identical overrides and throws an IllegalStateException to signal the configuration error to the user. To align with the behavior of @⁠MockBean and @⁠SpyBean in Spring Boot, and to help developers avoid scenarios that are potentially confusing or difficult to debug, this commit rejects identical bean overrides in the Spring TestContext Framework. Note, however, that it is still possible for a bean override to override a logically equivalent bean override. For example, a @⁠TestBean can override a @⁠MockitoBean, and vice versa. Closes gh-34054
1 parent 2137750 commit cd60a00

File tree

6 files changed

+39
-189
lines changed

6 files changed

+39
-189
lines changed

Diff for: spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactory.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.springframework.test.context.ContextConfigurationAttributes;
2525
import org.springframework.test.context.ContextCustomizerFactory;
2626
import org.springframework.test.context.TestContextAnnotationUtils;
27+
import org.springframework.util.Assert;
2728

2829
/**
2930
* {@link ContextCustomizerFactory} implementation that provides support for
@@ -51,10 +52,13 @@ public BeanOverrideContextCustomizer createContextCustomizer(Class<?> testClass,
5152
}
5253

5354
private void findBeanOverrideHandler(Class<?> testClass, Set<BeanOverrideHandler> handlers) {
54-
handlers.addAll(BeanOverrideHandler.forTestClass(testClass));
5555
if (TestContextAnnotationUtils.searchEnclosingClass(testClass)) {
5656
findBeanOverrideHandler(testClass.getEnclosingClass(), handlers);
5757
}
58+
BeanOverrideHandler.forTestClass(testClass).forEach(handler ->
59+
Assert.state(handlers.add(handler), () ->
60+
"Duplicate BeanOverrideHandler discovered in test class %s: %s"
61+
.formatted(testClass.getName(), handler)));
5862
}
5963

6064
}

Diff for: spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactoryTests.java

+25-6
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,20 @@
1919
import java.util.Collections;
2020
import java.util.function.Consumer;
2121

22-
import org.junit.jupiter.api.Nested;
2322
import org.junit.jupiter.api.Test;
2423

2524
import org.springframework.lang.Nullable;
2625
import org.springframework.test.context.bean.override.DummyBean.DummyBeanOverrideProcessor.DummyBeanOverrideHandler;
2726

2827
import static org.assertj.core.api.Assertions.assertThat;
28+
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
2929

3030
/**
3131
* Tests for {@link BeanOverrideContextCustomizerFactory}.
3232
*
3333
* @author Stephane Nicoll
34+
* @author Sam Brannen
35+
* @since 6.2
3436
*/
3537
class BeanOverrideContextCustomizerFactoryTests {
3638

@@ -65,6 +67,15 @@ void createContextCustomizerWhenNestedTestHasBeanOverrideAsWellAsTheParent() {
6567
.hasSize(2);
6668
}
6769

70+
@Test // gh-34054
71+
void failsWithDuplicateBeanOverrides() {
72+
Class<?> testClass = DuplicateOverridesTestCase.class;
73+
assertThatIllegalStateException()
74+
.isThrownBy(() -> createContextCustomizer(testClass))
75+
.withMessageStartingWith("Duplicate BeanOverrideHandler discovered in test class " + testClass.getName())
76+
.withMessageContaining("DummyBeanOverrideHandler");
77+
}
78+
6879

6980
private Consumer<BeanOverrideHandler> dummyHandler(@Nullable String beanName, Class<?> beanType) {
7081
return dummyHandler(beanName, beanType, BeanOverrideStrategy.REPLACE);
@@ -80,33 +91,41 @@ private Consumer<BeanOverrideHandler> dummyHandler(@Nullable String beanName, Cl
8091
}
8192

8293
@Nullable
83-
BeanOverrideContextCustomizer createContextCustomizer(Class<?> testClass) {
94+
private BeanOverrideContextCustomizer createContextCustomizer(Class<?> testClass) {
8495
return this.factory.createContextCustomizer(testClass, Collections.emptyList());
8596
}
8697

98+
8799
static class Test1 {
88100

89101
@DummyBean
90102
private String descriptor;
91-
92103
}
93104

94105
static class Test2 {
95106

96107
@DummyBean
97108
private String name;
98109

99-
@Nested
110+
// @Nested
100111
class Orange {
101112
}
102113

103-
@Nested
114+
// @Nested
104115
class Green {
105116

106117
@DummyBean(beanName = "counterBean")
107118
private Integer counter;
108-
109119
}
110120
}
111121

122+
static class DuplicateOverridesTestCase {
123+
124+
@DummyBean(beanName = "text")
125+
String text1;
126+
127+
@DummyBean(beanName = "text")
128+
String text2;
129+
}
130+
112131
}

Diff for: spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanForByNameLookupIntegrationTests.java

+6-42
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,6 @@ public class TestBeanForByNameLookupIntegrationTests {
4040
@TestBean(name = "field")
4141
String field;
4242

43-
@TestBean(name = "field")
44-
String renamed;
45-
4643
@TestBean(name = "methodRenamed1", methodName = "field")
4744
String methodRenamed1;
4845

@@ -60,12 +57,6 @@ void fieldHasOverride(ApplicationContext ctx) {
6057
assertThat(field).as("injection point").isEqualTo("fieldOverride");
6158
}
6259

63-
@Test
64-
void renamedFieldHasOverride(ApplicationContext ctx) {
65-
assertThat(ctx.getBean("field")).as("applicationContext").isEqualTo("fieldOverride");
66-
assertThat(renamed).as("injection point").isEqualTo("fieldOverride");
67-
}
68-
6960
@Test
7061
void fieldWithMethodNameHasOverride(ApplicationContext ctx) {
7162
assertThat(ctx.getBean("methodRenamed1")).as("applicationContext").isEqualTo("fieldOverride");
@@ -80,9 +71,6 @@ public class TestBeanFieldInEnclosingClassTests {
8071
@TestBean(name = "nestedField")
8172
String nestedField;
8273

83-
@TestBean(name = "nestedField")
84-
String renamed2;
85-
8674
@TestBean(name = "methodRenamed2", methodName = "nestedField")
8775
String methodRenamed2;
8876

@@ -93,12 +81,6 @@ void fieldHasOverride(ApplicationContext ctx) {
9381
assertThat(field).as("injection point").isEqualTo("fieldOverride");
9482
}
9583

96-
@Test
97-
void renamedFieldHasOverride(ApplicationContext ctx) {
98-
assertThat(ctx.getBean("field")).as("applicationContext").isEqualTo("fieldOverride");
99-
assertThat(renamed).as("injection point").isEqualTo("fieldOverride");
100-
}
101-
10284
@Test
10385
void fieldWithMethodNameHasOverride(ApplicationContext ctx) {
10486
assertThat(ctx.getBean("methodRenamed1")).as("applicationContext").isEqualTo("fieldOverride");
@@ -111,12 +93,6 @@ void nestedFieldHasOverride(ApplicationContext ctx) {
11193
assertThat(nestedField).isEqualTo("nestedFieldOverride");
11294
}
11395

114-
@Test
115-
void nestedRenamedFieldHasOverride(ApplicationContext ctx) {
116-
assertThat(ctx.getBean("nestedField")).as("applicationContext").isEqualTo("nestedFieldOverride");
117-
assertThat(renamed2).isEqualTo("nestedFieldOverride");
118-
}
119-
12096
@Test
12197
void nestedFieldWithMethodNameHasOverride(ApplicationContext ctx) {
12298
assertThat(ctx.getBean("methodRenamed2")).as("applicationContext").isEqualTo("nestedFieldOverride");
@@ -133,12 +109,6 @@ void fieldHasOverride(ApplicationContext ctx) {
133109
assertThat(field).as("injection point").isEqualTo("fieldOverride");
134110
}
135111

136-
@Test
137-
void renamedFieldHasOverride(ApplicationContext ctx) {
138-
assertThat(ctx.getBean("field")).as("applicationContext").isEqualTo("fieldOverride");
139-
assertThat(renamed).as("injection point").isEqualTo("fieldOverride");
140-
}
141-
142112
@Test
143113
void fieldWithMethodNameHasOverride(ApplicationContext ctx) {
144114
assertThat(ctx.getBean("methodRenamed1")).as("applicationContext").isEqualTo("fieldOverride");
@@ -151,12 +121,6 @@ void nestedFieldHasOverride(ApplicationContext ctx) {
151121
assertThat(nestedField).isEqualTo("nestedFieldOverride");
152122
}
153123

154-
@Test
155-
void nestedRenamedFieldHasOverride(ApplicationContext ctx) {
156-
assertThat(ctx.getBean("nestedField")).as("applicationContext").isEqualTo("nestedFieldOverride");
157-
assertThat(renamed2).isEqualTo("nestedFieldOverride");
158-
}
159-
160124
@Test
161125
void nestedFieldWithMethodNameHasOverride(ApplicationContext ctx) {
162126
assertThat(ctx.getBean("methodRenamed2")).as("applicationContext").isEqualTo("nestedFieldOverride");
@@ -170,25 +134,25 @@ void nestedFieldWithMethodNameHasOverride(ApplicationContext ctx) {
170134
public class TestBeanFactoryMethodInEnclosingClassTests {
171135

172136
@TestBean(methodName = "nestedField", name = "nestedField")
173-
String nestedField2;
137+
String nestedField;
174138

175139
@Test
176140
void nestedFieldHasOverride(ApplicationContext ctx) {
177141
assertThat(ctx.getBean("nestedField")).as("applicationContext").isEqualTo("nestedFieldOverride");
178-
assertThat(nestedField2).isEqualTo("nestedFieldOverride");
142+
assertThat(nestedField).isEqualTo("nestedFieldOverride");
179143
}
180144

181145
@Nested
182146
@DisplayName("With factory method in the enclosing class of the enclosing class")
183147
public class TestBeanFactoryMethodInEnclosingClassLevel2Tests {
184148

185-
@TestBean(methodName = "nestedField", name = "nestedField")
186-
String nestedField2;
149+
@TestBean(methodName = "nestedField", name = "nestedNestedField")
150+
String nestedNestedField;
187151

188152
@Test
189153
void nestedFieldHasOverride(ApplicationContext ctx) {
190-
assertThat(ctx.getBean("nestedField")).as("applicationContext").isEqualTo("nestedFieldOverride");
191-
assertThat(nestedField2).isEqualTo("nestedFieldOverride");
154+
assertThat(ctx.getBean("nestedNestedField")).as("applicationContext").isEqualTo("nestedFieldOverride");
155+
assertThat(nestedNestedField).isEqualTo("nestedFieldOverride");
192156
}
193157
}
194158
}

Diff for: spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanForByNameLookupIntegrationTests.java

+3-18
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,6 @@ public class MockitoBeanForByNameLookupIntegrationTests {
4545
@MockitoBean("field")
4646
ExampleService field;
4747

48-
@MockitoBean("field")
49-
ExampleService renamed;
50-
5148
@MockitoBean("nonExistingBean")
5249
ExampleService nonExisting;
5350

@@ -57,11 +54,9 @@ void fieldAndRenamedFieldHaveSameOverride(ApplicationContext ctx) {
5754
assertThat(ctx.getBean("field"))
5855
.isInstanceOf(ExampleService.class)
5956
.satisfies(MockitoAssertions::assertIsMock)
60-
.isSameAs(field)
61-
.isSameAs(renamed);
57+
.isSameAs(field);
6258

6359
assertThat(field.greeting()).as("mocked greeting").isNull();
64-
assertThat(renamed.greeting()).as("mocked greeting").isNull();
6560
}
6661

6762
@Test
@@ -83,20 +78,13 @@ public class MockitoBeanNestedTests {
8378
@Qualifier("field")
8479
ExampleService localField;
8580

86-
@Autowired
87-
@Qualifier("field")
88-
ExampleService localRenamed;
89-
9081
@Autowired
9182
@Qualifier("nonExistingBean")
9283
ExampleService localNonExisting;
9384

9485
@MockitoBean("nestedField")
9586
ExampleService nestedField;
9687

97-
@MockitoBean("nestedField")
98-
ExampleService nestedRenamed;
99-
10088
@MockitoBean("nestedNonExistingBean")
10189
ExampleService nestedNonExisting;
10290

@@ -106,11 +94,9 @@ void fieldAndRenamedFieldHaveSameOverride(ApplicationContext ctx) {
10694
assertThat(ctx.getBean("field"))
10795
.isInstanceOf(ExampleService.class)
10896
.satisfies(MockitoAssertions::assertIsMock)
109-
.isSameAs(localField)
110-
.isSameAs(localRenamed);
97+
.isSameAs(localField);
11198

11299
assertThat(localField.greeting()).as("mocked greeting").isNull();
113-
assertThat(localRenamed.greeting()).as("mocked greeting").isNull();
114100
}
115101

116102
@Test
@@ -128,8 +114,7 @@ void nestedFieldAndRenamedFieldHaveSameOverride(ApplicationContext ctx) {
128114
assertThat(ctx.getBean("nestedField"))
129115
.isInstanceOf(ExampleService.class)
130116
.satisfies(MockitoAssertions::assertIsMock)
131-
.isSameAs(nestedField)
132-
.isSameAs(nestedRenamed);
117+
.isSameAs(nestedField);
133118
}
134119

135120
@Test

Diff for: spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanDuplicateTypeAndNameIntegrationTests.java

-83
This file was deleted.

0 commit comments

Comments
 (0)