Skip to content

Commit 8727d72

Browse files
committed
Polish "Bean Overriding in Tests" support
1 parent ce8758b commit 8727d72

14 files changed

+134
-123
lines changed

Diff for: framework-docs/modules/ROOT/nav.adoc

+3-3
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,13 @@
134134
**** xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[]
135135
**** xref:testing/testcontext-framework/ctx-management/hierarchies.adoc[]
136136
*** xref:testing/testcontext-framework/fixture-di.adoc[]
137+
*** xref:testing/testcontext-framework/bean-overriding.adoc[]
137138
*** xref:testing/testcontext-framework/web-scoped-beans.adoc[]
138139
*** xref:testing/testcontext-framework/tx.adoc[]
139140
*** xref:testing/testcontext-framework/executing-sql.adoc[]
140141
*** xref:testing/testcontext-framework/parallel-test-execution.adoc[]
141142
*** xref:testing/testcontext-framework/support-classes.adoc[]
142143
*** xref:testing/testcontext-framework/aot.adoc[]
143-
*** xref:testing/testcontext-framework/bean-overriding.adoc[]
144144
** xref:testing/webtestclient.adoc[]
145145
** xref:testing/spring-mvc-test-framework.adoc[]
146146
*** xref:testing/spring-mvc-test-framework/server.adoc[]
@@ -172,6 +172,8 @@
172172
***** xref:testing/annotations/integration-spring/annotation-activeprofiles.adoc[]
173173
***** xref:testing/annotations/integration-spring/annotation-testpropertysource.adoc[]
174174
***** xref:testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc[]
175+
***** xref:testing/annotations/integration-spring/annotation-testbean.adoc[]
176+
***** xref:testing/annotations/integration-spring/annotation-mockitobean.adoc[]
175177
***** xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[]
176178
***** xref:testing/annotations/integration-spring/annotation-testexecutionlisteners.adoc[]
177179
***** xref:testing/annotations/integration-spring/annotation-recordapplicationevents.adoc[]
@@ -184,8 +186,6 @@
184186
***** xref:testing/annotations/integration-spring/annotation-sqlmergemode.adoc[]
185187
***** xref:testing/annotations/integration-spring/annotation-sqlgroup.adoc[]
186188
***** xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[]
187-
***** xref:testing/annotations/integration-spring/annotation-testbean.adoc[]
188-
***** xref:testing/annotations/integration-spring/annotation-mockitobean.adoc[]
189189
**** xref:testing/annotations/integration-junit4.adoc[]
190190
**** xref:testing/annotations/integration-junit-jupiter.adoc[]
191191
**** xref:testing/annotations/integration-meta.adoc[]

Diff for: framework-docs/modules/ROOT/pages/testing/annotations/integration-spring.adoc

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ Spring's testing annotations include the following:
1616
* xref:testing/annotations/integration-spring/annotation-activeprofiles.adoc[`@ActiveProfiles`]
1717
* xref:testing/annotations/integration-spring/annotation-testpropertysource.adoc[`@TestPropertySource`]
1818
* xref:testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc[`@DynamicPropertySource`]
19+
* xref:testing/annotations/integration-spring/annotation-testbean.adoc[`@TestBean`]
20+
* xref:testing/annotations/integration-spring/annotation-mockitobean.adoc[`@MockitoBean` and `@MockitoSpyBean`]
1921
* xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[`@DirtiesContext`]
2022
* xref:testing/annotations/integration-spring/annotation-testexecutionlisteners.adoc[`@TestExecutionListeners`]
2123
* xref:testing/annotations/integration-spring/annotation-recordapplicationevents.adoc[`@RecordApplicationEvents`]
@@ -28,6 +30,4 @@ Spring's testing annotations include the following:
2830
* xref:testing/annotations/integration-spring/annotation-sqlmergemode.adoc[`@SqlMergeMode`]
2931
* xref:testing/annotations/integration-spring/annotation-sqlgroup.adoc[`@SqlGroup`]
3032
* xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[`@DisabledInAotMode`]
31-
* xref:testing/annotations/integration-spring/annotation-testbean.adoc[`@TestBean`]
32-
* xref:testing/annotations/integration-spring/annotation-mockitobean.adoc[`@MockitoBean` and `@MockitoSpyBean`]
3333

Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
[[spring-testing-annotation-beanoverriding-mockitobean]]
22
= `@MockitoBean` and `@MockitoSpyBean`
33

4-
`@MockitoBean` and `@MockitoSpyBean` are used on a test class field to override a bean
5-
with a mocking and spying instance, respectively. In the later case, the original bean
6-
definition is not replaced but instead an early instance is captured and wrapped by the
7-
spy.
4+
`@MockitoBean` and `@MockitoSpyBean` are used on test class fields to override beans in
5+
the test's `ApplicationContext` with a Mockito mock or spy, respectively. In the latter
6+
case, the original bean definition is not replaced, but instead an early instance of the
7+
bean is captured and wrapped by the spy.
88

99
By default, the name of the bean to override is derived from the annotated field's name,
10-
but both annotations allows for a specific `name` to be provided. Each annotation also
10+
but both annotations allow for a specific `name` to be provided. Each annotation also
1111
defines Mockito-specific attributes to fine-tune the mocking details.
1212

13-
The `@MockitoBean` annotation uses the `CREATE_OR_REPLACE_DEFINITION`
13+
The `@MockitoBean` annotation uses the `REPLACE_OR_CREATE_DEFINITION`
1414
xref:testing/testcontext-framework/bean-overriding.adoc#spring-testing-beanoverriding-extending[strategy for test bean overriding].
1515

16-
The `@MockitoSpyBean` annotation uses the `WRAP_EARLY_BEAN`
17-
xref:testing/testcontext-framework/bean-overriding.adoc#spring-testing-beanoverriding-extending[strategy]
16+
The `@MockitoSpyBean` annotation uses the `WRAP_BEAN`
17+
xref:testing/testcontext-framework/bean-overriding.adoc#spring-testing-beanoverriding-extending[strategy],
1818
and the original instance is wrapped in a Mockito spy.
1919

20-
The following example shows how to configure the bean name for both `@MockitoBean` and
21-
`@MockitoSpyBean` annotations:
20+
The following example shows how to configure the bean name via `@MockitoBean` and
21+
`@MockitoSpyBean`:
2222

2323
[tabs]
2424
======
@@ -27,6 +27,7 @@ Java::
2727
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
2828
----
2929
class OverrideBeanTests {
30+
3031
@MockitoBean(name = "service1") // <1>
3132
private CustomService mockService;
3233
@@ -36,7 +37,7 @@ Java::
3637
// test case body...
3738
}
3839
----
39-
<1> Mark `mockService` as a Mockito mock override of bean `service1` in this test class
40-
<2> Mark `spyService` as a Mockito spy override of bean `service2` in this test class
41-
<3> Both fields will be injected with the Mockito values (the mock and the spy respectively)
42-
======
40+
<1> Mark `mockService` as a Mockito mock override of bean `service1` in this test class.
41+
<2> Mark `spyService` as a Mockito spy override of bean `service2` in this test class.
42+
<3> The fields will be injected with the Mockito mock and spy, respectively.
43+
======

Diff for: framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testbean.adoc

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
[[spring-testing-annotation-beanoverriding-testbean]]
22
= `@TestBean`
33

4-
`@TestBean` is used on a test class field to override a specific bean with an instance
5-
provided by a conventionally named static factory method.
4+
`@TestBean` is used on a test class field to override a specific bean in the test's
5+
`ApplicationContext` with an instance provided by a conventionally named static factory
6+
method.
67

78
By default, the bean name and the associated factory method name are derived from the
8-
annotated field's name but the annotation allows for specific values to be provided.
9+
annotated field's name, but the annotation allows for specific values to be provided.
910

1011
The `@TestBean` annotation uses the `REPLACE_DEFINITION`
1112
xref:testing/testcontext-framework/bean-overriding.adoc#spring-testing-beanoverriding-extending[strategy for test bean overriding].
1213

1314
The following example shows how to fully configure the `@TestBean` annotation, with
14-
explicit values equivalent to the default:
15+
explicit values equivalent to the defaults:
1516

1617
[tabs]
1718
======
@@ -30,10 +31,9 @@ Java::
3031
}
3132
}
3233
----
33-
<1> Mark a field for bean overriding in this test class
34-
<2> The result of this static method will be used as the instance and injected into the field
34+
<1> Mark a field for bean overriding in this test class.
35+
<2> The result of this static method will be used as the instance and injected into the field.
3536
======
3637

37-
NOTE: The method to invoke is searched in the test class and any enclosing class it might
38-
have, as well as its hierarchy. This typically allows nested test class to rely on the
39-
factory method in the root test class.
38+
NOTE: Spring searches for the factory method to invoke in the test class, in the test
39+
class hierarchy, and in the enclosing class hierarchy for a `@Nested` test class.
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,67 @@
1-
[[spring-testing-beanoverriding]]
1+
[[testcontext-bean-overriding]]
22
= Bean Overriding in Tests
33

4-
Bean Overriding in Tests refers to the ability to override specific beans in the Context
5-
for a test class, by annotating one or more fields in said test class.
4+
Bean overriding in tests refers to the ability to override specific beans in the
5+
`ApplicationContext` for a test class, by annotating one or more fields in the test class.
66

7-
NOTE: This is intended as a less risky alternative to the practice of registering a bean via
8-
`@Bean` with the `DefaultListableBeanFactory` `setAllowBeanDefinitionOverriding` set to
9-
`true`.
7+
NOTE: This feature is intended as a less risky alternative to the practice of registering
8+
a bean via `@Bean` with the `DefaultListableBeanFactory`
9+
`setAllowBeanDefinitionOverriding` flag set to `true`.
1010

11-
The Spring Testing Framework provides two sets of annotations:
12-
xref:testing/annotations/integration-spring/annotation-testbean.adoc[`@TestBean`],
13-
xref:testing/annotations/integration-spring/annotation-mockitobean.adoc[`@MockitoBean` and
14-
`@MockitoSpyBean`]. The former relies purely on Spring, while the later set relies on
15-
the https://site.mockito.org/[Mockito] third party library.
11+
The Spring TestContext framework provides two sets of annotations for bean overriding.
1612

17-
[[spring-testing-beanoverriding-extending]]
18-
== Extending bean override with a custom annotation
13+
* xref:testing/annotations/integration-spring/annotation-testbean.adoc[`@TestBean`]
14+
* xref:testing/annotations/integration-spring/annotation-mockitobean.adoc[`@MockitoBean` and `@MockitoSpyBean`]
1915

20-
The three annotations mentioned above build upon the `@BeanOverride` meta-annotation
21-
and associated infrastructure, which allows to define custom bean overriding variants.
16+
The former relies purely on Spring, while the latter set relies on the
17+
https://site.mockito.org/[Mockito] third-party library.
2218

23-
To create an extension, the following is needed:
19+
[[testcontext-bean-overriding-custom]]
20+
== Custom Bean Override Support
2421

25-
- An annotation meta-annotated with `@BeanOverride` that defines the
26-
`BeanOverrideProcessor` to use.
27-
- The `BeanOverrideProcessor` implementation itself.
28-
- One or more concrete `OverrideMetadata` implementations provided by the processor.
22+
The three annotations mentioned above build upon the `@BeanOverride` meta-annotation and
23+
associated infrastructure, which allows one to define custom bean overriding variants.
2924

30-
The Spring TestContext Framework includes infrastructure classes that support bean
31-
overriding: a `BeanFactoryPostProcessor`, a `TestExecutionListener` and a
32-
`ContextCustomizerFactory`.
33-
The later two are automatically registered via the Spring TestContext Framework
34-
`spring.factories` file, and are responsible for setting up the rest of the infrastructure.
25+
To create custom bean override support, the following is needed:
3526

36-
The test classes are parsed looking for any field meta-annotated with `@BeanOverride`,
37-
instantiating the relevant `BeanOverrideProcessor` in order to register an
38-
`OverrideMetadata`.
27+
* An annotation meta-annotated with `@BeanOverride` that defines the
28+
`BeanOverrideProcessor` to use
29+
* A custom `BeanOverrideProcessor` implementation
30+
* One or more concrete `OverrideMetadata` implementations provided by the processor
3931

40-
Then the `BeanOverrideBeanFactoryPostProcessor` will use that information to alter the
41-
context, registering and replacing bean definitions as defined by each metadata
42-
`BeanOverrideStrategy`:
32+
The Spring TestContext framework includes implementations of the following APIs that
33+
support bean overriding and are responsible for setting up the rest of the infrastructure.
4334

44-
- `REPLACE_DEFINITION`: replaces the bean definition. If it is not present in the
45-
context, an exception is thrown.
46-
- `CREATE_OR_REPLACE_DEFINITION`: replaces the bean definition if the bean definition
47-
does not exist, or create one if it is not.
48-
- `WRAP_BEAN`: get the original instance early so that it can be wrapped.
35+
* a `BeanFactoryPostProcessor`
36+
* a `ContextCustomizerFactory`
37+
* a `TestExecutionListener`
4938

50-
NOTE: The Bean Overriding infrastructure does not include any bean resolution step,
51-
unlike an `@Autowired`-annotated field for instance. As such, the name of the bean to
52-
override must be somehow provided to or computed by the `BeanOverrideProcessor`.
53-
Typically, the user provides the name one way or the other.
39+
The `spring-test` module registers implementations of the latter two
40+
(`BeanOverrideContextCustomizerFactory` and `BeanOverrideTestExecutionListener`) in its
41+
{spring-framework-code}/spring-test/src/main/resources/META-INF/spring.factories[`META-INF/spring.factories`
42+
properties file].
43+
44+
The bean overriding infrastructure searches in test classes for any field meta-annotated
45+
with `@BeanOverride` and instantiates the corresponding `BeanOverrideProcessor` which is
46+
responsible for registering appropriate `OverrideMetadata`.
47+
48+
The internal `BeanOverrideBeanFactoryPostProcessor` then uses that information to alter
49+
the test's `ApplicationContext` by registering and replacing bean definitions as defined
50+
by the corresponding `BeanOverrideStrategy`:
51+
52+
* `REPLACE_DEFINITION`: Replaces the bean definition. Throws an exception if a
53+
corresponding bean definition does not exist.
54+
* `REPLACE_OR_CREATE_DEFINITION`: Replaces the bean definition if it exists. Creates a
55+
new bean definition if a corresponding bean definition does not exist.
56+
* `WRAP_BEAN`: Retrieves the original bean instance and wraps it.
57+
58+
[NOTE]
59+
====
60+
In contrast to Spring's autowiring mechanism (for example, resolution of an `@Autowired`
61+
field), the bean overriding infrastructure in the TestContext framework does not perform
62+
any heuristics to locate a bean. Instead, the name of the bean to override must be
63+
explicitly provided to or computed by the `BeanOverrideProcessor`.
64+
65+
Typically, the user provides the bean name via a custom annotation, or the
66+
`BeanOverrideProcessor` determines the bean name based on some convention.
67+
====

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

+5-7
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,23 @@
2222
import java.lang.annotation.Target;
2323

2424
/**
25-
* Mark an annotation as eligible for Bean Override processing.
25+
* Mark a composed annotation as eligible for Bean Override processing.
2626
*
27-
* <p>Specifying this annotation triggers the defined {@link BeanOverrideProcessor}
27+
* <p>Specifying this annotation triggers the configured {@link BeanOverrideProcessor}
2828
* which must be capable of handling the composed annotation and its attributes.
2929
*
30-
* <p>The composed annotation is meant to be detected on fields only so it is
31-
* expected that it has a {@code Target} of {@link ElementType#FIELD FIELD}.
30+
* <p>Since the composed annotation should only be applied to fields, it is
31+
* expected that it has a {@link Target} of {@link ElementType#FIELD FIELD}.
3232
*
3333
* @author Simon Baslé
3434
* @since 6.2
35-
* @see BeanOverrideBeanFactoryPostProcessor
3635
*/
3736
@Retention(RetentionPolicy.RUNTIME)
3837
@Target(ElementType.ANNOTATION_TYPE)
3938
public @interface BeanOverride {
4039

4140
/**
42-
* The {@link BeanOverrideProcessor} implementation to trigger against
43-
* the composed annotation.
41+
* The {@link BeanOverrideProcessor} implementation to use.
4442
*/
4543
Class<? extends BeanOverrideProcessor> value();
4644

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

+4-6
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141

4242
/**
4343
* A {@link BeanFactoryPostProcessor} implementation that processes test classes
44-
* and adapt the {@link BeanFactory} for any {@link BeanOverride} it may define.
44+
* and adapts the {@link BeanFactory} for any {@link BeanOverride} it may define.
4545
*
4646
* <p>A set of classes from which to parse {@link OverrideMetadata} must be
4747
* provided to this processor. Each test class is expected to use any
@@ -51,7 +51,7 @@
5151
*
5252
* <p>The provided classes are fully parsed at creation to build a metadata set.
5353
* This processor implements several {@link BeanOverrideStrategy overriding
54-
* strategy} and chooses the correct one according to each override metadata
54+
* strategies} and chooses the correct one according to each override metadata's
5555
* {@link OverrideMetadata#getStrategy()} method. Additionally, it provides
5656
* support for injecting the overridden bean instances into their corresponding
5757
* annotated {@link Field fields}.
@@ -61,7 +61,6 @@
6161
*/
6262
class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {
6363

64-
6564
private final BeanOverrideRegistrar overrideRegistrar;
6665

6766

@@ -195,14 +194,13 @@ private Set<String> getExistingBeanNames(ConfigurableListableBeanFactory beanFac
195194
static final class WrapEarlyBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor,
196195
PriorityOrdered {
197196

198-
private final BeanOverrideRegistrar overrideRegistrar;
197+
private final Map<String, Object> earlyReferences = new ConcurrentHashMap<>(16);
199198

200-
private final Map<String, Object> earlyReferences;
199+
private final BeanOverrideRegistrar overrideRegistrar;
201200

202201

203202
private WrapEarlyBeanPostProcessor(BeanOverrideRegistrar registrar) {
204203
this.overrideRegistrar = registrar;
205-
this.earlyReferences = new ConcurrentHashMap<>(16);
206204
}
207205

208206

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

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ static void registerInfrastructure(BeanDefinitionRegistry registry, Set<Class<?>
6969

7070
private static void addInfrastructureBeanDefinition(BeanDefinitionRegistry registry,
7171
Class<?> clazz, String beanName, Consumer<ConstructorArgumentValues> constructorArgumentsConsumer) {
72+
7273
if (!registry.containsBeanDefinition(beanName)) {
7374
RootBeanDefinition definition = new RootBeanDefinition(clazz);
7475
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);

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

+2-3
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,11 @@ static Set<OverrideMetadata> parse(Class<?> clazz) {
8484
private static void parseField(Field field, Class<?> testClass, Set<OverrideMetadata> metadataSet) {
8585
AtomicBoolean overrideAnnotationFound = new AtomicBoolean();
8686
MergedAnnotations.from(field, DIRECT).stream(BeanOverride.class).forEach(mergedAnnotation -> {
87-
Assert.state(mergedAnnotation.isMetaPresent(), "@BeanOverride annotation must be meta-present");
87+
MergedAnnotation<?> metaSource = mergedAnnotation.getMetaSource();
88+
Assert.state(metaSource != null, "@BeanOverride annotation must be meta-present");
8889

8990
BeanOverride beanOverride = mergedAnnotation.synthesize();
9091
BeanOverrideProcessor processor = BeanUtils.instantiateClass(beanOverride.value());
91-
MergedAnnotation<?> metaSource = mergedAnnotation.getMetaSource();
92-
Assert.state(metaSource != null, "Meta-annotation source must not be null");
9392
Annotation composedAnnotation = metaSource.synthesize();
9493

9594
Assert.state(overrideAnnotationFound.compareAndSet(false, true),

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@
2020
import java.lang.reflect.Field;
2121

2222
/**
23-
* Strategy interface for Bean Override processing, providing an
23+
* Strategy interface for Bean Override processing, providing
2424
* {@link OverrideMetadata} that drives how the target bean is overridden.
2525
*
26-
* <p>At least one composed annotations meta-annotated with
27-
* {@link BeanOverride @BeanOverride}) is a companion of this processor and
26+
* <p>At least one composed annotation that is meta-annotated with
27+
* {@link BeanOverride @BeanOverride} must be a companion of this processor and
2828
* may provide additional user settings that drive how the concrete
2929
* {@link OverrideMetadata} is configured.
3030
*

0 commit comments

Comments
 (0)