Skip to content

Commit 87c93d3

Browse files
committed
Revise bean override tests with a focus on AOT support
As a follow up to the previous commit (31f8e12), this commit polishes bean override tests and revises them with a focus on AOT testing support and simplified maintenance. - Introduce EngineTestKitUtils to simplify working with JUnit's EngineTestKit. - Use idiomatic EngineTestKit APIs to simplify assertions on EngineTestKit results. - Introduce BeanOverrideTestSuite to simplify running all bean override tests within the IDE. - Separate failure and success scenario tests, so that failure tests do not launch the JUnit Platform to run tests using the Spring TestContext Framework (TCF) within a test class that itself uses the TCF. - Make AbstractTestBeanIntegrationTestCase actually abstract. - Rename test case classes to give them meaningful names and simplify understanding of what's being tested. - Ensure tests for @⁠MockitoSpyBean functionality use @⁠MockitoSpyBean instead of @⁠MockitoBean. - Declare @⁠Configuration classes local to @⁠SpringJUnitConfig test classes whenever possible. See gh-29122 See gh-32925
1 parent 31f8e12 commit 87c93d3

19 files changed

+1044
-681
lines changed

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ private void registerReplaceDefinition(ConfigurableListableBeanFactory beanFacto
133133
Set<String> candidates = getExistingBeanNamesByType(beanFactory, overrideMetadata, true);
134134
if (candidates.size() != 1) {
135135
Field f = overrideMetadata.getField();
136-
throw new IllegalStateException("Unable to select a bean definition to override, " +
136+
throw new IllegalStateException("Unable to select a bean definition to override: " +
137137
candidates.size() + " bean definitions found of type " + overrideMetadata.getBeanType() +
138138
" (as required by annotated field '" + f.getDeclaringClass().getSimpleName() +
139139
"." + f.getName() + "')");
@@ -147,7 +147,7 @@ private void registerReplaceDefinition(ConfigurableListableBeanFactory beanFacto
147147
existingBeanDefinition = beanFactory.getBeanDefinition(beanName);
148148
}
149149
else if (enforceExistingDefinition) {
150-
throw new IllegalStateException("Unable to override bean '" + beanName + "'; there is no" +
150+
throw new IllegalStateException("Unable to override bean '" + beanName + "': there is no" +
151151
" bean definition to replace with that name of type " + overrideMetadata.getBeanType());
152152
}
153153
}
@@ -183,7 +183,7 @@ private void registerWrapBean(ConfigurableListableBeanFactory beanFactory, Overr
183183
Set<String> candidateNames = getExistingBeanNamesByType(beanFactory, metadata, true);
184184
if (candidateNames.size() != 1) {
185185
Field f = metadata.getField();
186-
throw new IllegalStateException("Unable to select a bean to override by wrapping, " +
186+
throw new IllegalStateException("Unable to select a bean to override by wrapping: " +
187187
candidateNames.size() + " bean instances found of type " + metadata.getBeanType() +
188188
" (as required by annotated field '" + f.getDeclaringClass().getSimpleName() +
189189
"." + f.getName() + "')");
@@ -193,7 +193,7 @@ private void registerWrapBean(ConfigurableListableBeanFactory beanFactory, Overr
193193
else {
194194
Set<String> candidates = getExistingBeanNamesByType(beanFactory, metadata, false);
195195
if (!candidates.contains(beanName)) {
196-
throw new IllegalStateException("Unable to override bean '" + beanName + "' by wrapping; there is no" +
196+
throw new IllegalStateException("Unable to override bean '" + beanName + "' by wrapping: there is no" +
197197
" existing bean instance with that name of type " + metadata.getBeanType());
198198
}
199199
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ void cannotReplaceIfNoBeanMatching() {
7777

7878
assertThatIllegalStateException()
7979
.isThrownBy(context::refresh)
80-
.withMessage("Unable to override bean 'explicit'; there is no bean definition " +
80+
.withMessage("Unable to override bean 'explicit': there is no bean definition " +
8181
"to replace with that name of type org.springframework.test.context.bean.override.example.ExampleService");
8282
}
8383

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2002-2024 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;
18+
19+
import org.junit.jupiter.api.ClassOrderer;
20+
import org.junit.platform.suite.api.ConfigurationParameter;
21+
import org.junit.platform.suite.api.ExcludeTags;
22+
import org.junit.platform.suite.api.IncludeClassNamePatterns;
23+
import org.junit.platform.suite.api.IncludeEngines;
24+
import org.junit.platform.suite.api.SelectPackages;
25+
import org.junit.platform.suite.api.Suite;
26+
27+
/**
28+
* JUnit Platform based test suite for tests that involve bean override support
29+
* in the Spring TestContext Framework.
30+
*
31+
* <p><strong>This suite is only intended to be used manually within an IDE.</strong>
32+
*
33+
* <h3>Logging Configuration</h3>
34+
*
35+
* <p>In order for our log4j2 configuration to be used in an IDE, you must
36+
* set the following system property before running any tests &mdash; for
37+
* example, in <em>Run Configurations</em> in Eclipse.
38+
*
39+
* <pre style="code">
40+
* -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
41+
* </pre>
42+
*
43+
* @author Sam Brannen
44+
* @since 6.2
45+
*/
46+
@Suite
47+
@IncludeEngines("junit-jupiter")
48+
@SelectPackages("org.springframework.test.context.bean.override")
49+
@IncludeClassNamePatterns(".*Tests$")
50+
@ExcludeTags("failing-test-case")
51+
@ConfigurationParameter(
52+
key = ClassOrderer.DEFAULT_ORDER_PROPERTY_NAME,
53+
value = "org.junit.jupiter.api.ClassOrderer$ClassName"
54+
)
55+
public class BeanOverrideTestSuite {
56+
}

spring-test/src/test/java/org/springframework/test/context/bean/override/convention/AbstractTestBeanIntegrationTestCase.java

+18-16
Original file line numberDiff line numberDiff line change
@@ -21,28 +21,28 @@
2121
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
2222

2323
@SpringJUnitConfig
24-
class AbstractTestBeanIntegrationTestCase {
24+
abstract class AbstractTestBeanIntegrationTestCase {
2525

26-
@TestBean(name = "someBean")
27-
Pojo someBean;
26+
@TestBean(name = "someBean")
27+
Pojo someBean;
2828

29-
@TestBean(name = "otherBean")
30-
Pojo otherBean;
29+
@TestBean(name = "otherBean")
30+
Pojo otherBean;
3131

32-
@TestBean(name = "thirdBean")
33-
Pojo anotherBean;
32+
@TestBean(name = "thirdBean")
33+
Pojo anotherBean;
3434

35-
static Pojo otherBeanTestOverride() {
36-
return new FakePojo("otherBean in superclass");
37-
}
35+
static Pojo otherBeanTestOverride() {
36+
return new FakePojo("otherBean in superclass");
37+
}
3838

39-
static Pojo thirdBeanTestOverride() {
40-
return new FakePojo("third in superclass");
41-
}
39+
static Pojo thirdBeanTestOverride() {
40+
return new FakePojo("third in superclass");
41+
}
4242

43-
static Pojo commonBeanOverride() {
44-
return new FakePojo("in superclass");
45-
}
43+
static Pojo commonBeanOverride() {
44+
return new FakePojo("in superclass");
45+
}
4646

4747
interface Pojo {
4848

@@ -60,6 +60,7 @@ protected FakePojo(String value) {
6060
this.value = value;
6161
}
6262

63+
@Override
6364
public String getValue() {
6465
return this.value;
6566
}
@@ -94,4 +95,5 @@ Pojo pojo2() {
9495
return new ProdPojo();
9596
}
9697
}
98+
9799
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Copyright 2002-2024 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.Test;
20+
21+
import org.springframework.context.annotation.Bean;
22+
import org.springframework.context.annotation.Configuration;
23+
import org.springframework.test.context.bean.override.example.ExampleService;
24+
import org.springframework.test.context.bean.override.example.RealExampleService;
25+
import org.springframework.test.context.junit.EngineTestKitUtils;
26+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
27+
28+
import static org.assertj.core.api.Assertions.fail;
29+
import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure;
30+
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.cause;
31+
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf;
32+
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message;
33+
34+
/**
35+
* {@link TestBean @TestBean} "by type" integration tests for failure scenarios.
36+
*
37+
* @author Simon Baslé
38+
* @author Sam Brannen
39+
* @since 6.2
40+
* @see TestBeanByTypeIntegrationTests
41+
*/
42+
class FailingTestBeanByTypeIntegrationTests {
43+
44+
@Test
45+
void zeroCandidates() {
46+
Class<?> testClass = NoMatchingBeansTestCase.class;
47+
EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1,
48+
finishedWithFailure(
49+
cause(
50+
instanceOf(IllegalStateException.class),
51+
message("""
52+
Unable to select a bean definition to override: 0 bean definitions \
53+
found of type %s (as required by annotated field '%s.example')"""
54+
.formatted(ExampleService.class.getName(), testClass.getSimpleName())))));
55+
}
56+
57+
@Test
58+
void tooManyCandidates() {
59+
Class<?> testClass = TooManyBeansTestCase.class;
60+
EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1,
61+
finishedWithFailure(
62+
cause(
63+
instanceOf(IllegalStateException.class),
64+
message("""
65+
Unable to select a bean definition to override: 2 bean definitions \
66+
found of type %s (as required by annotated field '%s.example')"""
67+
.formatted(ExampleService.class.getName(), testClass.getSimpleName())))));
68+
}
69+
70+
71+
@SpringJUnitConfig
72+
static class NoMatchingBeansTestCase {
73+
74+
@TestBean
75+
ExampleService example;
76+
77+
@Test
78+
void test() {
79+
}
80+
81+
static ExampleService exampleTestOverride() {
82+
return fail("unexpected override");
83+
}
84+
85+
@Configuration
86+
static class Config {
87+
}
88+
}
89+
90+
91+
@SpringJUnitConfig
92+
static class TooManyBeansTestCase {
93+
94+
@TestBean
95+
ExampleService example;
96+
97+
@Test
98+
void test() {
99+
}
100+
101+
static ExampleService exampleTestOverride() {
102+
return fail("unexpected override");
103+
}
104+
105+
@Configuration
106+
static class Config {
107+
108+
@Bean
109+
ExampleService bean1() {
110+
return new RealExampleService("1 Hello");
111+
}
112+
113+
@Bean
114+
ExampleService bean2() {
115+
return new RealExampleService("2 Hello");
116+
}
117+
}
118+
}
119+
120+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright 2002-2024 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.Test;
20+
21+
import org.springframework.test.context.junit.EngineTestKitUtils;
22+
23+
import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure;
24+
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf;
25+
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message;
26+
import static org.springframework.test.context.junit.EngineTestKitUtils.rootCause;
27+
28+
/**
29+
* {@link TestBean @TestBean} inheritance integration tests for failure scenarios.
30+
*
31+
* @author Simon Baslé
32+
* @author Sam Brannen
33+
* @since 6.2
34+
* @see TestBeanInheritanceIntegrationTests
35+
*/
36+
class FailingTestBeanInheritanceIntegrationTests {
37+
38+
@Test
39+
void failsIfFieldInSupertypeButNoMethod() {
40+
Class<?> testClass = FieldInSupertypeButNoMethodTestCase.class;
41+
EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1,
42+
finishedWithFailure(
43+
rootCause(
44+
instanceOf(IllegalStateException.class),
45+
message("""
46+
Failed to find a static test bean factory method in %s with return type %s \
47+
whose name matches one of the supported candidates [someBeanTestOverride]"""
48+
.formatted(testClass.getName(), AbstractTestBeanIntegrationTestCase.Pojo.class.getName())))));
49+
}
50+
51+
@Test
52+
void failsIfMethod1InSupertypeAndMethod2InType() {
53+
Class<?> testClass = Method1InSupertypeAndMethod2InTypeTestCase.class;
54+
EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1,
55+
finishedWithFailure(
56+
rootCause(
57+
instanceOf(IllegalStateException.class),
58+
message("""
59+
Found 2 competing static test bean factory methods in %s with return type %s \
60+
whose name matches one of the supported candidates \
61+
[thirdBeanTestOverride, anotherBeanTestOverride]"""
62+
.formatted(testClass.getName(), AbstractTestBeanIntegrationTestCase.Pojo.class.getName())))));
63+
}
64+
65+
66+
static class FieldInSupertypeButNoMethodTestCase extends AbstractTestBeanIntegrationTestCase {
67+
68+
@Test
69+
void test() {
70+
}
71+
}
72+
73+
static class Method1InSupertypeAndMethod2InTypeTestCase extends AbstractTestBeanIntegrationTestCase {
74+
75+
static Pojo someBeanTestOverride() {
76+
return new FakePojo("ignored");
77+
}
78+
79+
static Pojo anotherBeanTestOverride() {
80+
return new FakePojo("sub2");
81+
}
82+
83+
@Test
84+
void test() {
85+
}
86+
}
87+
88+
}

0 commit comments

Comments
 (0)