Document bean override in TestContext framework section of the manual

This change splits the documentation in the reference manual: the
`@TestBean`, `@MockitoBean` and `@MockitoSpyBean` annotations are kept
in the appendix and the general documentation about the feature is moved
into a dedicated sub-section of the TCF section.

Close gh-32490
This commit is contained in:
Simon Baslé 2024-04-10 16:45:46 +02:00
parent afbce96fb7
commit 02ee5de470
7 changed files with 140 additions and 138 deletions

View File

@ -140,6 +140,7 @@
*** xref:testing/testcontext-framework/parallel-test-execution.adoc[]
*** xref:testing/testcontext-framework/support-classes.adoc[]
*** xref:testing/testcontext-framework/aot.adoc[]
*** xref:testing/testcontext-framework/bean-overriding.adoc[]
** xref:testing/webtestclient.adoc[]
** xref:testing/spring-mvc-test-framework.adoc[]
*** xref:testing/spring-mvc-test-framework/server.adoc[]
@ -183,7 +184,8 @@
***** 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-spring/annotation-testbean.adoc[]
***** xref:testing/annotations/integration-spring/annotation-mockitobean.adoc[]
**** xref:testing/annotations/integration-junit4.adoc[]
**** xref:testing/annotations/integration-junit-jupiter.adoc[]
**** xref:testing/annotations/integration-meta.adoc[]

View File

@ -89,7 +89,7 @@ adapt your configuration accordingly. While not recommended, you can silence tho
by setting the `allowBeanDefinitionOverriding` flag to `true`.
NOTE: We acknowledge that overriding beans in a test is convenient, and there is
explicit support for this. For more details please refer to xref:testing/annotations/integration-spring/annotation-beanoverriding.adoc[this section].
explicit support for this. For more details please refer to xref:testing/testcontext-framework/bean-overriding.adoc[this section].
[[beans-beanname]]
== Naming Beans

View File

@ -28,6 +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`]
* xref:testing/annotations/integration-spring/annotation-testbean.adoc[`@TestBean`]
* xref:testing/annotations/integration-spring/annotation-mockitobean.adoc[`@MockitoBean` and `@MockitoSpyBean`]

View File

@ -1,134 +0,0 @@
[[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
======
NOTE: The method to invoke is searched in the test class and any enclosing class it might
have, as well as its hierarchy. This typically allows nested test class to rely on the
method to use in the root test class.
[[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.
To create an extension, the following is needed:
- An annotation meta-annotated with `@BeanOverride` that defines the
`BeanOverrideProcessor` to use.
- The `BeanOverrideProcessor` implementation itself.
- One or more concrete `OverrideMetadata` implementations provided by the processor.
The Spring TestContext Framework includes infrastructure classes that support bean
overriding: a `BeanFactoryPostProcessor`, a `TestExecutionListener` and a
`ContextCustomizerFactory`.
The later two are automatically registered via the Spring TestContext Framework
`spring.factories` file, and are responsible for setting up the rest of the infrastructure.
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 `BeanOverrideBeanFactoryPostProcessor` will use that information to alter the
context, registering and replacing bean definitions as defined by each metadata
`BeanOverrideStrategy`:
- `REPLACE_DEFINITION`: replaces the bean definition. If it is not present in the
context, an exception is thrown.
- `CREATE_OR_REPLACE_DEFINITION`: replaces the bean definition if the bean definition
does not exist, or create one if it is not.
- `WRAP_BEAN`: get the original instance early so that it can be wrapped.
NOTE: The Bean Overriding infrastructure does not include any bean resolution step,
unlike an `@Autowired`-annotated field for instance. As such, the name of the bean to
override must be somehow provided to or computed by the `BeanOverrideProcessor`.
Typically, the user provides the name one way or the other.

View File

@ -0,0 +1,42 @@
[[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:testing/testcontext-framework/bean-overriding.adoc#spring-testing-beanoverriding-extending[strategy for test bean overriding].
The `@MockitoSpyBean` annotation uses the `WRAP_EARLY_BEAN`
xref:testing/testcontext-framework/bean-overriding.adoc#spring-testing-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)
======

View File

@ -0,0 +1,39 @@
[[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 factory method.
By default, the bean name and the associated factory 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:testing/testcontext-framework/bean-overriding.adoc#spring-testing-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
======
NOTE: The method to invoke is searched in the test class and any enclosing class it might
have, as well as its hierarchy. This typically allows nested test class to rely on the
factory method in the root test class.

View File

@ -0,0 +1,53 @@
[[spring-testing-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:
xref:testing/annotations/integration-spring/annotation-testbean.adoc[`@TestBean`],
xref:testing/annotations/integration-spring/annotation-mockitobean.adoc[`@MockitoBean` and
`@MockitoSpyBean`]. The former relies purely on Spring, while the later set relies on
the https://site.mockito.org/[Mockito] third party library.
[[spring-testing-beanoverriding-extending]]
== Extending bean override with a custom annotation
The three annotations mentioned above build upon the `@BeanOverride` meta-annotation
and associated infrastructure, which allows to define custom bean overriding variants.
To create an extension, the following is needed:
- An annotation meta-annotated with `@BeanOverride` that defines the
`BeanOverrideProcessor` to use.
- The `BeanOverrideProcessor` implementation itself.
- One or more concrete `OverrideMetadata` implementations provided by the processor.
The Spring TestContext Framework includes infrastructure classes that support bean
overriding: a `BeanFactoryPostProcessor`, a `TestExecutionListener` and a
`ContextCustomizerFactory`.
The later two are automatically registered via the Spring TestContext Framework
`spring.factories` file, and are responsible for setting up the rest of the infrastructure.
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 `BeanOverrideBeanFactoryPostProcessor` will use that information to alter the
context, registering and replacing bean definitions as defined by each metadata
`BeanOverrideStrategy`:
- `REPLACE_DEFINITION`: replaces the bean definition. If it is not present in the
context, an exception is thrown.
- `CREATE_OR_REPLACE_DEFINITION`: replaces the bean definition if the bean definition
does not exist, or create one if it is not.
- `WRAP_BEAN`: get the original instance early so that it can be wrapped.
NOTE: The Bean Overriding infrastructure does not include any bean resolution step,
unlike an `@Autowired`-annotated field for instance. As such, the name of the bean to
override must be somehow provided to or computed by the `BeanOverrideProcessor`.
Typically, the user provides the name one way or the other.