diff --git a/framework-docs/modules/ROOT/nav.adoc b/framework-docs/modules/ROOT/nav.adoc index aa42ae76d92..dbf0c19d5f4 100644 --- a/framework-docs/modules/ROOT/nav.adoc +++ b/framework-docs/modules/ROOT/nav.adoc @@ -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[] diff --git a/framework-docs/modules/ROOT/pages/core/beans/definition.adoc b/framework-docs/modules/ROOT/pages/core/beans/definition.adoc index ca6730e4074..80c0175ecaf 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/definition.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/definition.adoc @@ -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 diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring.adoc index 997f717ee73..0d7eaaff63d 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring.adoc @@ -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`] diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-beanoverriding.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-beanoverriding.adoc deleted file mode 100644 index 3639b88fb06..00000000000 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-beanoverriding.adoc +++ /dev/null @@ -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. \ No newline at end of file diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-mockitobean.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-mockitobean.adoc new file mode 100644 index 00000000000..c1270dff19e --- /dev/null +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-mockitobean.adoc @@ -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) +====== \ No newline at end of file diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testbean.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testbean.adoc new file mode 100644 index 00000000000..4c8117f59f4 --- /dev/null +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testbean.adoc @@ -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. \ No newline at end of file diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/bean-overriding.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/bean-overriding.adoc new file mode 100644 index 00000000000..a5e384fdd0e --- /dev/null +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/bean-overriding.adoc @@ -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. \ No newline at end of file