Add support for bean overriding in tests
This commit introduces two sets of annotations (`@TestBean` on one side and `MockitoBean`/`MockitoSpyBean` on the other side), as well as an extension mecanism based on the `@BeanOverride` meta-annotation. Extension implementors are expected to only provide an annotation, a BeanOverrideProcessor implementation and an OverrideMetadata subclass. Closes gh-29917.
This commit is contained in:
@@ -183,6 +183,7 @@
|
||||
***** xref:testing/annotations/integration-spring/annotation-sqlmergemode.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-sqlgroup.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[]
|
||||
***** xref:testing/annotations/integration-spring/annotation-beanoverriding.adoc[]
|
||||
**** xref:testing/annotations/integration-junit4.adoc[]
|
||||
**** xref:testing/annotations/integration-junit-jupiter.adoc[]
|
||||
**** xref:testing/annotations/integration-meta.adoc[]
|
||||
|
||||
@@ -28,4 +28,6 @@ Spring's testing annotations include the following:
|
||||
* xref:testing/annotations/integration-spring/annotation-sqlmergemode.adoc[`@SqlMergeMode`]
|
||||
* xref:testing/annotations/integration-spring/annotation-sqlgroup.adoc[`@SqlGroup`]
|
||||
* xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[`@DisabledInAotMode`]
|
||||
* xref:testing/annotations/integration-spring/annotation-beanoverriding.adoc#spring-testing-annotation-beanoverriding-testbean[`@TestBean`]
|
||||
* xref:testing/annotations/integration-spring/annotation-beanoverriding.adoc#spring-testing-annotation-beanoverriding-mockitobean[`@MockitoBean` and `@MockitoSpyBean`]
|
||||
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
[[spring-testing-annotation-beanoverriding]]
|
||||
= Bean Overriding in Tests
|
||||
|
||||
Bean Overriding in Tests refers to the ability to override specific beans in the Context
|
||||
for a test class, by annotating one or more fields in said test class.
|
||||
|
||||
NOTE: This is intended as a less risky alternative to the practice of registering a bean via
|
||||
`@Bean` with the `DefaultListableBeanFactory` `setAllowBeanDefinitionOverriding` set to
|
||||
`true`.
|
||||
|
||||
The Spring Testing Framework provides two sets of annotations presented below. One relies
|
||||
purely on Spring, while the second set relies on the Mockito third party library.
|
||||
|
||||
[[spring-testing-annotation-beanoverriding-testbean]]
|
||||
== `@TestBean`
|
||||
|
||||
`@TestBean` is used on a test class field to override a specific bean with an instance
|
||||
provided by a conventionally named static method.
|
||||
|
||||
By default, the bean name and the associated static method name are derived from the
|
||||
annotated field's name, but the annotation allows for specific values to be provided.
|
||||
|
||||
The `@TestBean` annotation uses the `REPLACE_DEFINITION`
|
||||
xref:#spring-testing-annotation-beanoverriding-extending[strategy for test bean overriding].
|
||||
|
||||
The following example shows how to fully configure the `@TestBean` annotation, with
|
||||
explicit values equivalent to the default:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
class OverrideBeanTests {
|
||||
@TestBean(name = "service", methodName = "serviceTestOverride") // <1>
|
||||
private CustomService service;
|
||||
|
||||
// test case body...
|
||||
|
||||
private static CustomService serviceTestOverride() { // <2>
|
||||
return new MyFakeCustomService();
|
||||
}
|
||||
}
|
||||
----
|
||||
<1> Mark a field for bean overriding in this test class
|
||||
<2> The result of this static method will be used as the instance and injected into the field
|
||||
======
|
||||
|
||||
|
||||
[[spring-testing-annotation-beanoverriding-mockitobean]]
|
||||
== `@MockitoBean` and `@MockitoSpyBean`
|
||||
|
||||
`@MockitoBean` and `@MockitoSpyBean` are used on a test class field to override a bean
|
||||
with a mocking and spying instance, respectively. In the later case, the original bean
|
||||
definition is not replaced but instead an early instance is captured and wrapped by the
|
||||
spy.
|
||||
|
||||
By default, the name of the bean to override is derived from the annotated field's name,
|
||||
but both annotations allows for a specific `name` to be provided. Each annotation also
|
||||
defines Mockito-specific attributes to fine-tune the mocking details.
|
||||
|
||||
The `@MockitoBean` annotation uses the `CREATE_OR_REPLACE_DEFINITION`
|
||||
xref:#spring-testing-annotation-beanoverriding-extending[strategy for test bean overriding].
|
||||
|
||||
The `@MockitoSpyBean` annotation uses the `WRAP_EARLY_BEAN`
|
||||
xref:#spring-testing-annotation-beanoverriding-extending[strategy] and the original instance
|
||||
is wrapped in a Mockito spy.
|
||||
|
||||
The following example shows how to configure the bean name for both `@MockitoBean` and
|
||||
`@MockitoSpyBean` annotations:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
class OverrideBeanTests {
|
||||
@MockitoBean(name = "service1") // <1>
|
||||
private CustomService mockService;
|
||||
|
||||
@MockitoSpyBean(name = "service2") // <2>
|
||||
private CustomService spyService; // <3>
|
||||
|
||||
// test case body...
|
||||
}
|
||||
----
|
||||
<1> Mark `mockService` as a Mockito mock override of bean `service1` in this test class
|
||||
<2> Mark `spyService` as a Mockito spy override of bean `service2` in this test class
|
||||
<3> Both fields will be injected with the Mockito values (the mock and the spy respectively)
|
||||
======
|
||||
|
||||
|
||||
[[spring-testing-annotation-beanoverriding-extending]]
|
||||
== Extending bean override with a custom annotation
|
||||
|
||||
The three annotations introduced above build upon the `@BeanOverride` meta-annotation
|
||||
and associated infrastructure, which allows to define custom bean overriding variants.
|
||||
|
||||
In order to provide an extension, three classes are needed:
|
||||
- a concrete `BeanOverrideProcessor` `<P>`
|
||||
- a concrete `OverrideMetadata` created by said processor
|
||||
- an annotation meta-annotated with `@BeanOverride(P.class)`
|
||||
|
||||
The Spring TestContext Framework includes infrastructure classes that support bean
|
||||
overriding: a `BeanPostProcessor`, a `TestExecutionListener` and a `ContextCustomizerFactory`.
|
||||
These are automatically registered via the Spring TestContext Framework `spring.factories`
|
||||
file.
|
||||
|
||||
The test classes are parsed looking for any field meta-annotated with `@BeanOverride`,
|
||||
instantiating the relevant `BeanOverrideProcessor` in order to register an `OverrideMetadata`.
|
||||
|
||||
Then the `BeanOverrideBeanPostProcessor` will use that information to alter the Context,
|
||||
registering and replacing bean definitions as influenced by each metadata
|
||||
`BeanOverrideStrategy`:
|
||||
|
||||
- `REPLACE_DEFINITION`: the bean post-processor replaces the bean definition.
|
||||
If it is not present in the context, an exception is thrown.
|
||||
- `CREATE_OR_REPLACE_DEFINITION`: same as above but if the bean definition is not present
|
||||
in the context, one is created
|
||||
- `WRAP_EARLY_BEAN`: an original instance is obtained via
|
||||
`SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference(Object, String)` and
|
||||
provided to the processor during `OverrideMetadata` creation.
|
||||
|
||||
NOTE: The Bean Overriding infrastructure works best with singleton beans. It also doesn't
|
||||
include any bean resolution (unlike e.g. an `@Autowired`-annotated field). As such, the
|
||||
name of the bean to override MUST be somehow provided to or computed by the
|
||||
`BeanOverrideProcessor`. Typically, the end user provides the name as part of the custom
|
||||
annotation's attributes, or the annotated field's name.
|
||||
Reference in New Issue
Block a user