Browse Source

Polish "Bean Overriding in Tests" support

pull/32653/head
Sam Brannen 2 years ago
parent
commit
8727d723f3
  1. 6
      framework-docs/modules/ROOT/nav.adoc
  2. 4
      framework-docs/modules/ROOT/pages/testing/annotations/integration-spring.adoc
  3. 29
      framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-mockitobean.adoc
  4. 18
      framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testbean.adoc
  5. 94
      framework-docs/modules/ROOT/pages/testing/testcontext-framework/bean-overriding.adoc
  6. 12
      spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverride.java
  7. 10
      spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java
  8. 1
      spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizer.java
  9. 5
      spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideParsingUtils.java
  10. 6
      spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideProcessor.java
  11. 17
      spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideRegistrar.java
  12. 7
      spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideStrategy.java
  13. 19
      spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideTestExecutionListener.java
  14. 29
      spring-test/src/main/java/org/springframework/test/context/bean/override/OverrideMetadata.java

6
framework-docs/modules/ROOT/nav.adoc

@ -134,13 +134,13 @@
**** xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[] **** xref:testing/testcontext-framework/ctx-management/failure-threshold.adoc[]
**** xref:testing/testcontext-framework/ctx-management/hierarchies.adoc[] **** xref:testing/testcontext-framework/ctx-management/hierarchies.adoc[]
*** xref:testing/testcontext-framework/fixture-di.adoc[] *** xref:testing/testcontext-framework/fixture-di.adoc[]
*** xref:testing/testcontext-framework/bean-overriding.adoc[]
*** xref:testing/testcontext-framework/web-scoped-beans.adoc[] *** xref:testing/testcontext-framework/web-scoped-beans.adoc[]
*** xref:testing/testcontext-framework/tx.adoc[] *** xref:testing/testcontext-framework/tx.adoc[]
*** xref:testing/testcontext-framework/executing-sql.adoc[] *** xref:testing/testcontext-framework/executing-sql.adoc[]
*** xref:testing/testcontext-framework/parallel-test-execution.adoc[] *** xref:testing/testcontext-framework/parallel-test-execution.adoc[]
*** xref:testing/testcontext-framework/support-classes.adoc[] *** xref:testing/testcontext-framework/support-classes.adoc[]
*** xref:testing/testcontext-framework/aot.adoc[] *** xref:testing/testcontext-framework/aot.adoc[]
*** xref:testing/testcontext-framework/bean-overriding.adoc[]
** xref:testing/webtestclient.adoc[] ** xref:testing/webtestclient.adoc[]
** xref:testing/spring-mvc-test-framework.adoc[] ** xref:testing/spring-mvc-test-framework.adoc[]
*** xref:testing/spring-mvc-test-framework/server.adoc[] *** xref:testing/spring-mvc-test-framework/server.adoc[]
@ -172,6 +172,8 @@
***** xref:testing/annotations/integration-spring/annotation-activeprofiles.adoc[] ***** xref:testing/annotations/integration-spring/annotation-activeprofiles.adoc[]
***** xref:testing/annotations/integration-spring/annotation-testpropertysource.adoc[] ***** xref:testing/annotations/integration-spring/annotation-testpropertysource.adoc[]
***** xref:testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc[] ***** xref:testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc[]
***** xref:testing/annotations/integration-spring/annotation-testbean.adoc[]
***** xref:testing/annotations/integration-spring/annotation-mockitobean.adoc[]
***** xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[] ***** xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[]
***** xref:testing/annotations/integration-spring/annotation-testexecutionlisteners.adoc[] ***** xref:testing/annotations/integration-spring/annotation-testexecutionlisteners.adoc[]
***** xref:testing/annotations/integration-spring/annotation-recordapplicationevents.adoc[] ***** xref:testing/annotations/integration-spring/annotation-recordapplicationevents.adoc[]
@ -184,8 +186,6 @@
***** xref:testing/annotations/integration-spring/annotation-sqlmergemode.adoc[] ***** xref:testing/annotations/integration-spring/annotation-sqlmergemode.adoc[]
***** xref:testing/annotations/integration-spring/annotation-sqlgroup.adoc[] ***** xref:testing/annotations/integration-spring/annotation-sqlgroup.adoc[]
***** xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[] ***** xref:testing/annotations/integration-spring/annotation-disabledinaotmode.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-junit4.adoc[]
**** xref:testing/annotations/integration-junit-jupiter.adoc[] **** xref:testing/annotations/integration-junit-jupiter.adoc[]
**** xref:testing/annotations/integration-meta.adoc[] **** xref:testing/annotations/integration-meta.adoc[]

4
framework-docs/modules/ROOT/pages/testing/annotations/integration-spring.adoc

@ -16,6 +16,8 @@ Spring's testing annotations include the following:
* xref:testing/annotations/integration-spring/annotation-activeprofiles.adoc[`@ActiveProfiles`] * xref:testing/annotations/integration-spring/annotation-activeprofiles.adoc[`@ActiveProfiles`]
* xref:testing/annotations/integration-spring/annotation-testpropertysource.adoc[`@TestPropertySource`] * xref:testing/annotations/integration-spring/annotation-testpropertysource.adoc[`@TestPropertySource`]
* xref:testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc[`@DynamicPropertySource`] * xref:testing/annotations/integration-spring/annotation-dynamicpropertysource.adoc[`@DynamicPropertySource`]
* xref:testing/annotations/integration-spring/annotation-testbean.adoc[`@TestBean`]
* xref:testing/annotations/integration-spring/annotation-mockitobean.adoc[`@MockitoBean` and `@MockitoSpyBean`]
* xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[`@DirtiesContext`] * xref:testing/annotations/integration-spring/annotation-dirtiescontext.adoc[`@DirtiesContext`]
* xref:testing/annotations/integration-spring/annotation-testexecutionlisteners.adoc[`@TestExecutionListeners`] * xref:testing/annotations/integration-spring/annotation-testexecutionlisteners.adoc[`@TestExecutionListeners`]
* xref:testing/annotations/integration-spring/annotation-recordapplicationevents.adoc[`@RecordApplicationEvents`] * xref:testing/annotations/integration-spring/annotation-recordapplicationevents.adoc[`@RecordApplicationEvents`]
@ -28,6 +30,4 @@ Spring's testing annotations include the following:
* xref:testing/annotations/integration-spring/annotation-sqlmergemode.adoc[`@SqlMergeMode`] * xref:testing/annotations/integration-spring/annotation-sqlmergemode.adoc[`@SqlMergeMode`]
* xref:testing/annotations/integration-spring/annotation-sqlgroup.adoc[`@SqlGroup`] * xref:testing/annotations/integration-spring/annotation-sqlgroup.adoc[`@SqlGroup`]
* xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[`@DisabledInAotMode`] * xref:testing/annotations/integration-spring/annotation-disabledinaotmode.adoc[`@DisabledInAotMode`]
* xref:testing/annotations/integration-spring/annotation-testbean.adoc[`@TestBean`]
* xref:testing/annotations/integration-spring/annotation-mockitobean.adoc[`@MockitoBean` and `@MockitoSpyBean`]

29
framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-mockitobean.adoc

@ -1,24 +1,24 @@
[[spring-testing-annotation-beanoverriding-mockitobean]] [[spring-testing-annotation-beanoverriding-mockitobean]]
= `@MockitoBean` and `@MockitoSpyBean` = `@MockitoBean` and `@MockitoSpyBean`
`@MockitoBean` and `@MockitoSpyBean` are used on a test class field to override a bean `@MockitoBean` and `@MockitoSpyBean` are used on test class fields to override beans in
with a mocking and spying instance, respectively. In the later case, the original bean the test's `ApplicationContext` with a Mockito mock or spy, respectively. In the latter
definition is not replaced but instead an early instance is captured and wrapped by the case, the original bean definition is not replaced, but instead an early instance of the
spy. bean is captured and wrapped by the spy.
By default, the name of the bean to override is derived from the annotated field's name, 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 but both annotations allow for a specific `name` to be provided. Each annotation also
defines Mockito-specific attributes to fine-tune the mocking details. defines Mockito-specific attributes to fine-tune the mocking details.
The `@MockitoBean` annotation uses the `CREATE_OR_REPLACE_DEFINITION` The `@MockitoBean` annotation uses the `REPLACE_OR_CREATE_DEFINITION`
xref:testing/testcontext-framework/bean-overriding.adoc#spring-testing-beanoverriding-extending[strategy for test bean overriding]. xref:testing/testcontext-framework/bean-overriding.adoc#spring-testing-beanoverriding-extending[strategy for test bean overriding].
The `@MockitoSpyBean` annotation uses the `WRAP_EARLY_BEAN` The `@MockitoSpyBean` annotation uses the `WRAP_BEAN`
xref:testing/testcontext-framework/bean-overriding.adoc#spring-testing-beanoverriding-extending[strategy] xref:testing/testcontext-framework/bean-overriding.adoc#spring-testing-beanoverriding-extending[strategy],
and the original instance is wrapped in a Mockito spy. and the original instance is wrapped in a Mockito spy.
The following example shows how to configure the bean name for both `@MockitoBean` and The following example shows how to configure the bean name via `@MockitoBean` and
`@MockitoSpyBean` annotations: `@MockitoSpyBean`:
[tabs] [tabs]
====== ======
@ -27,6 +27,7 @@ Java::
[source,java,indent=0,subs="verbatim,quotes",role="primary"] [source,java,indent=0,subs="verbatim,quotes",role="primary"]
---- ----
class OverrideBeanTests { class OverrideBeanTests {
@MockitoBean(name = "service1") // <1> @MockitoBean(name = "service1") // <1>
private CustomService mockService; private CustomService mockService;
@ -36,7 +37,7 @@ Java::
// test case body... // test case body...
} }
---- ----
<1> Mark `mockService` as a Mockito mock override of bean `service1` in this test class <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 <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) <3> The fields will be injected with the Mockito mock and spy, respectively.
====== ======

18
framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testbean.adoc

@ -1,17 +1,18 @@
[[spring-testing-annotation-beanoverriding-testbean]] [[spring-testing-annotation-beanoverriding-testbean]]
= `@TestBean` = `@TestBean`
`@TestBean` is used on a test class field to override a specific bean with an instance `@TestBean` is used on a test class field to override a specific bean in the test's
provided by a conventionally named static factory method. `ApplicationContext` 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 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. annotated field's name, but the annotation allows for specific values to be provided.
The `@TestBean` annotation uses the `REPLACE_DEFINITION` The `@TestBean` annotation uses the `REPLACE_DEFINITION`
xref:testing/testcontext-framework/bean-overriding.adoc#spring-testing-beanoverriding-extending[strategy for test bean overriding]. 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 The following example shows how to fully configure the `@TestBean` annotation, with
explicit values equivalent to the default: explicit values equivalent to the defaults:
[tabs] [tabs]
====== ======
@ -30,10 +31,9 @@ Java::
} }
} }
---- ----
<1> Mark a field for bean overriding in this test class <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 <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 NOTE: Spring searches for the factory method to invoke in the test class, in the test
have, as well as its hierarchy. This typically allows nested test class to rely on the class hierarchy, and in the enclosing class hierarchy for a `@Nested` test class.
factory method in the root test class.

94
framework-docs/modules/ROOT/pages/testing/testcontext-framework/bean-overriding.adoc

@ -1,53 +1,67 @@
[[spring-testing-beanoverriding]] [[testcontext-bean-overriding]]
= Bean Overriding in Tests = Bean Overriding in Tests
Bean Overriding in Tests refers to the ability to override specific beans in the Context Bean overriding in tests refers to the ability to override specific beans in the
for a test class, by annotating one or more fields in said test class. `ApplicationContext` for a test class, by annotating one or more fields in the test class.
NOTE: This is intended as a less risky alternative to the practice of registering a bean via NOTE: This feature is intended as a less risky alternative to the practice of registering
`@Bean` with the `DefaultListableBeanFactory` `setAllowBeanDefinitionOverriding` set to a bean via `@Bean` with the `DefaultListableBeanFactory`
`true`. `setAllowBeanDefinitionOverriding` flag set to `true`.
The Spring Testing Framework provides two sets of annotations: The Spring TestContext framework provides two sets of annotations for bean overriding.
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]] * xref:testing/annotations/integration-spring/annotation-testbean.adoc[`@TestBean`]
== Extending bean override with a custom annotation * xref:testing/annotations/integration-spring/annotation-mockitobean.adoc[`@MockitoBean` and `@MockitoSpyBean`]
The three annotations mentioned above build upon the `@BeanOverride` meta-annotation The former relies purely on Spring, while the latter set relies on the
and associated infrastructure, which allows to define custom bean overriding variants. https://site.mockito.org/[Mockito] third-party library.
To create an extension, the following is needed: [[testcontext-bean-overriding-custom]]
== Custom Bean Override Support
- An annotation meta-annotated with `@BeanOverride` that defines the The three annotations mentioned above build upon the `@BeanOverride` meta-annotation and
`BeanOverrideProcessor` to use. associated infrastructure, which allows one to define custom bean overriding variants.
- The `BeanOverrideProcessor` implementation itself.
- One or more concrete `OverrideMetadata` implementations provided by the processor.
The Spring TestContext Framework includes infrastructure classes that support bean To create custom bean override support, the following is needed:
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`, * An annotation meta-annotated with `@BeanOverride` that defines the
instantiating the relevant `BeanOverrideProcessor` in order to register an `BeanOverrideProcessor` to use
`OverrideMetadata`. * A custom `BeanOverrideProcessor` implementation
* One or more concrete `OverrideMetadata` implementations provided by the processor
Then the `BeanOverrideBeanFactoryPostProcessor` will use that information to alter the The Spring TestContext framework includes implementations of the following APIs that
context, registering and replacing bean definitions as defined by each metadata support bean overriding and are responsible for setting up the rest of the infrastructure.
`BeanOverrideStrategy`:
- `REPLACE_DEFINITION`: replaces the bean definition. If it is not present in the * a `BeanFactoryPostProcessor`
context, an exception is thrown. * a `ContextCustomizerFactory`
- `CREATE_OR_REPLACE_DEFINITION`: replaces the bean definition if the bean definition * a `TestExecutionListener`
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, The `spring-test` module registers implementations of the latter two
unlike an `@Autowired`-annotated field for instance. As such, the name of the bean to (`BeanOverrideContextCustomizerFactory` and `BeanOverrideTestExecutionListener`) in its
override must be somehow provided to or computed by the `BeanOverrideProcessor`. {spring-framework-code}/spring-test/src/main/resources/META-INF/spring.factories[`META-INF/spring.factories`
Typically, the user provides the name one way or the other. properties file].
The bean overriding infrastructure searches in test classes for any field meta-annotated
with `@BeanOverride` and instantiates the corresponding `BeanOverrideProcessor` which is
responsible for registering appropriate `OverrideMetadata`.
The internal `BeanOverrideBeanFactoryPostProcessor` then uses that information to alter
the test's `ApplicationContext` by registering and replacing bean definitions as defined
by the corresponding `BeanOverrideStrategy`:
* `REPLACE_DEFINITION`: Replaces the bean definition. Throws an exception if a
corresponding bean definition does not exist.
* `REPLACE_OR_CREATE_DEFINITION`: Replaces the bean definition if it exists. Creates a
new bean definition if a corresponding bean definition does not exist.
* `WRAP_BEAN`: Retrieves the original bean instance and wraps it.
[NOTE]
====
In contrast to Spring's autowiring mechanism (for example, resolution of an `@Autowired`
field), the bean overriding infrastructure in the TestContext framework does not perform
any heuristics to locate a bean. Instead, the name of the bean to override must be
explicitly provided to or computed by the `BeanOverrideProcessor`.
Typically, the user provides the bean name via a custom annotation, or the
`BeanOverrideProcessor` determines the bean name based on some convention.
====

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

@ -22,25 +22,23 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/** /**
* Mark an annotation as eligible for Bean Override processing. * Mark a composed annotation as eligible for Bean Override processing.
* *
* <p>Specifying this annotation triggers the defined {@link BeanOverrideProcessor} * <p>Specifying this annotation triggers the configured {@link BeanOverrideProcessor}
* which must be capable of handling the composed annotation and its attributes. * which must be capable of handling the composed annotation and its attributes.
* *
* <p>The composed annotation is meant to be detected on fields only so it is * <p>Since the composed annotation should only be applied to fields, it is
* expected that it has a {@code Target} of {@link ElementType#FIELD FIELD}. * expected that it has a {@link Target} of {@link ElementType#FIELD FIELD}.
* *
* @author Simon Baslé * @author Simon Baslé
* @since 6.2 * @since 6.2
* @see BeanOverrideBeanFactoryPostProcessor
*/ */
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE) @Target(ElementType.ANNOTATION_TYPE)
public @interface BeanOverride { public @interface BeanOverride {
/** /**
* The {@link BeanOverrideProcessor} implementation to trigger against * The {@link BeanOverrideProcessor} implementation to use.
* the composed annotation.
*/ */
Class<? extends BeanOverrideProcessor> value(); Class<? extends BeanOverrideProcessor> value();

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

@ -41,7 +41,7 @@ import org.springframework.util.StringUtils;
/** /**
* A {@link BeanFactoryPostProcessor} implementation that processes test classes * A {@link BeanFactoryPostProcessor} implementation that processes test classes
* and adapt the {@link BeanFactory} for any {@link BeanOverride} it may define. * and adapts the {@link BeanFactory} for any {@link BeanOverride} it may define.
* *
* <p>A set of classes from which to parse {@link OverrideMetadata} must be * <p>A set of classes from which to parse {@link OverrideMetadata} must be
* provided to this processor. Each test class is expected to use any * provided to this processor. Each test class is expected to use any
@ -51,7 +51,7 @@ import org.springframework.util.StringUtils;
* *
* <p>The provided classes are fully parsed at creation to build a metadata set. * <p>The provided classes are fully parsed at creation to build a metadata set.
* This processor implements several {@link BeanOverrideStrategy overriding * This processor implements several {@link BeanOverrideStrategy overriding
* strategy} and chooses the correct one according to each override metadata * strategies} and chooses the correct one according to each override metadata's
* {@link OverrideMetadata#getStrategy()} method. Additionally, it provides * {@link OverrideMetadata#getStrategy()} method. Additionally, it provides
* support for injecting the overridden bean instances into their corresponding * support for injecting the overridden bean instances into their corresponding
* annotated {@link Field fields}. * annotated {@link Field fields}.
@ -61,7 +61,6 @@ import org.springframework.util.StringUtils;
*/ */
class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered { class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {
private final BeanOverrideRegistrar overrideRegistrar; private final BeanOverrideRegistrar overrideRegistrar;
@ -195,14 +194,13 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
static final class WrapEarlyBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor, static final class WrapEarlyBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor,
PriorityOrdered { PriorityOrdered {
private final BeanOverrideRegistrar overrideRegistrar; private final Map<String, Object> earlyReferences = new ConcurrentHashMap<>(16);
private final Map<String, Object> earlyReferences; private final BeanOverrideRegistrar overrideRegistrar;
private WrapEarlyBeanPostProcessor(BeanOverrideRegistrar registrar) { private WrapEarlyBeanPostProcessor(BeanOverrideRegistrar registrar) {
this.overrideRegistrar = registrar; this.overrideRegistrar = registrar;
this.earlyReferences = new ConcurrentHashMap<>(16);
} }

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

@ -69,6 +69,7 @@ class BeanOverrideContextCustomizer implements ContextCustomizer {
private static void addInfrastructureBeanDefinition(BeanDefinitionRegistry registry, private static void addInfrastructureBeanDefinition(BeanDefinitionRegistry registry,
Class<?> clazz, String beanName, Consumer<ConstructorArgumentValues> constructorArgumentsConsumer) { Class<?> clazz, String beanName, Consumer<ConstructorArgumentValues> constructorArgumentsConsumer) {
if (!registry.containsBeanDefinition(beanName)) { if (!registry.containsBeanDefinition(beanName)) {
RootBeanDefinition definition = new RootBeanDefinition(clazz); RootBeanDefinition definition = new RootBeanDefinition(clazz);
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);

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

@ -84,12 +84,11 @@ abstract class BeanOverrideParsingUtils {
private static void parseField(Field field, Class<?> testClass, Set<OverrideMetadata> metadataSet) { private static void parseField(Field field, Class<?> testClass, Set<OverrideMetadata> metadataSet) {
AtomicBoolean overrideAnnotationFound = new AtomicBoolean(); AtomicBoolean overrideAnnotationFound = new AtomicBoolean();
MergedAnnotations.from(field, DIRECT).stream(BeanOverride.class).forEach(mergedAnnotation -> { MergedAnnotations.from(field, DIRECT).stream(BeanOverride.class).forEach(mergedAnnotation -> {
Assert.state(mergedAnnotation.isMetaPresent(), "@BeanOverride annotation must be meta-present"); MergedAnnotation<?> metaSource = mergedAnnotation.getMetaSource();
Assert.state(metaSource != null, "@BeanOverride annotation must be meta-present");
BeanOverride beanOverride = mergedAnnotation.synthesize(); BeanOverride beanOverride = mergedAnnotation.synthesize();
BeanOverrideProcessor processor = BeanUtils.instantiateClass(beanOverride.value()); BeanOverrideProcessor processor = BeanUtils.instantiateClass(beanOverride.value());
MergedAnnotation<?> metaSource = mergedAnnotation.getMetaSource();
Assert.state(metaSource != null, "Meta-annotation source must not be null");
Annotation composedAnnotation = metaSource.synthesize(); Annotation composedAnnotation = metaSource.synthesize();
Assert.state(overrideAnnotationFound.compareAndSet(false, true), Assert.state(overrideAnnotationFound.compareAndSet(false, true),

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

@ -20,11 +20,11 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.Field; import java.lang.reflect.Field;
/** /**
* Strategy interface for Bean Override processing, providing an * Strategy interface for Bean Override processing, providing
* {@link OverrideMetadata} that drives how the target bean is overridden. * {@link OverrideMetadata} that drives how the target bean is overridden.
* *
* <p>At least one composed annotations meta-annotated with * <p>At least one composed annotation that is meta-annotated with
* {@link BeanOverride @BeanOverride}) is a companion of this processor and * {@link BeanOverride @BeanOverride} must be a companion of this processor and
* may provide additional user settings that drive how the concrete * may provide additional user settings that drive how the concrete
* {@link OverrideMetadata} is configured. * {@link OverrideMetadata} is configured.
* *

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

@ -41,23 +41,22 @@ import org.springframework.util.StringUtils;
*/ */
class BeanOverrideRegistrar implements BeanFactoryAware { class BeanOverrideRegistrar implements BeanFactoryAware {
private final Map<OverrideMetadata, String> beanNameRegistry; private final Map<OverrideMetadata, String> beanNameRegistry = new HashMap<>();
private final Map<String, OverrideMetadata> earlyOverrideMetadata; private final Map<String, OverrideMetadata> earlyOverrideMetadata = new HashMap<>();
private final Set<OverrideMetadata> overrideMetadata; private final Set<OverrideMetadata> overrideMetadata;
@Nullable @Nullable
private ConfigurableBeanFactory beanFactory; private ConfigurableBeanFactory beanFactory;
/** /**
* Create a new registrar and immediately parse the provided classes. * Create a new registrar and immediately parse the provided classes.
* @param classesToParse the initial set of classes that have been * @param classesToParse the initial set of classes that have been
* detected to contain bean overriding annotations * detected to contain bean overriding annotations
*/ */
BeanOverrideRegistrar(Set<Class<?>> classesToParse) { BeanOverrideRegistrar(Set<Class<?>> classesToParse) {
this.beanNameRegistry = new HashMap<>();
this.earlyOverrideMetadata = new HashMap<>();
this.overrideMetadata = BeanOverrideParsingUtils.parse(classesToParse); this.overrideMetadata = BeanOverrideParsingUtils.parse(classesToParse);
} }
@ -71,7 +70,7 @@ class BeanOverrideRegistrar implements BeanFactoryAware {
} }
/** /**
* Return the detected {@link OverrideMetadata} instances. * Get the detected {@link OverrideMetadata} instances.
*/ */
Set<OverrideMetadata> getOverrideMetadata() { Set<OverrideMetadata> getOverrideMetadata() {
return this.overrideMetadata; return this.overrideMetadata;
@ -85,16 +84,16 @@ class BeanOverrideRegistrar implements BeanFactoryAware {
Object wrapIfNecessary(Object bean, String beanName) throws BeansException { Object wrapIfNecessary(Object bean, String beanName) throws BeansException {
OverrideMetadata metadata = this.earlyOverrideMetadata.get(beanName); OverrideMetadata metadata = this.earlyOverrideMetadata.get(beanName);
if (metadata != null && metadata.getStrategy() == BeanOverrideStrategy.WRAP_BEAN) { if (metadata != null && metadata.getStrategy() == BeanOverrideStrategy.WRAP_BEAN) {
bean = metadata.createOverride(beanName, null, bean);
Assert.state(this.beanFactory != null, "ConfigurableBeanFactory must not be null"); Assert.state(this.beanFactory != null, "ConfigurableBeanFactory must not be null");
bean = metadata.createOverride(beanName, null, bean);
metadata.track(bean, this.beanFactory); metadata.track(bean, this.beanFactory);
} }
return bean; return bean;
} }
/** /**
* Register the provided {@link OverrideMetadata} and associate it with a * Register the provided {@link OverrideMetadata} and associate it with the
* {@code beanName}. * supplied {@code beanName}.
*/ */
void registerNameForMetadata(OverrideMetadata metadata, String beanName) { void registerNameForMetadata(OverrideMetadata metadata, String beanName) {
this.beanNameRegistry.put(metadata, beanName); this.beanNameRegistry.put(metadata, beanName);
@ -119,7 +118,7 @@ class BeanOverrideRegistrar implements BeanFactoryAware {
try { try {
ReflectionUtils.makeAccessible(field); ReflectionUtils.makeAccessible(field);
Object existingValue = ReflectionUtils.getField(field, target); Object existingValue = ReflectionUtils.getField(field, target);
Assert.state(this.beanFactory != null, "beanFactory must not be null"); Assert.state(this.beanFactory != null, "ConfigurableBeanFactory must not be null");
Object bean = this.beanFactory.getBean(beanName, field.getType()); Object bean = this.beanFactory.getBean(beanName, field.getType());
if (existingValue == bean) { if (existingValue == bean) {
return; return;

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

@ -35,7 +35,7 @@ public enum BeanOverrideStrategy {
/** /**
* Replace or create a given bean definition, immediately preparing a * Replace or create a given bean definition, immediately preparing a
* singleton instance. * singleton instance.
* <p>Contrary to {@link #REPLACE_DEFINITION} this create a new bean * <p>Contrary to {@link #REPLACE_DEFINITION}, this creates a new bean
* definition if the target bean definition does not exist rather than * definition if the target bean definition does not exist rather than
* failing. * failing.
*/ */
@ -43,8 +43,9 @@ public enum BeanOverrideStrategy {
/** /**
* Intercept and process an early bean reference rather than a bean * Intercept and process an early bean reference rather than a bean
* definition, allowing variants of bean overriding to wrap the instance. * definition, allowing variants of bean overriding to wrap the instance
* For instance, to delegate to actual methods in the context of a mocking "spy". * &mdash; for example, to delegate to actual methods in the context of a
* mocking "spy".
*/ */
WRAP_BEAN WRAP_BEAN

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

@ -20,15 +20,13 @@ import java.lang.reflect.Field;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import org.springframework.test.context.TestContext; import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestExecutionListener;
import org.springframework.test.context.support.AbstractTestExecutionListener; import org.springframework.test.context.support.AbstractTestExecutionListener;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
/** /**
* {@link TestExecutionListener} implementation that enables Bean Override * {@code TestExecutionListener} that enables Bean Override support in tests,
* support in tests, injecting overridden beans in appropriate fields of the * injecting overridden beans in appropriate fields of the test instance.
* test instance.
* *
* @author Simon Baslé * @author Simon Baslé
* @since 6.2 * @since 6.2
@ -54,9 +52,9 @@ public class BeanOverrideTestExecutionListener extends AbstractTestExecutionList
} }
/** /**
* Process the test instance and make sure that flagged fields for bean * Process the test instance and make sure that fields flagged for bean
* overriding are processed. Each field get is value updated with the * overriding are processed.
* overridden bean instance. * <p>Each field's value will be updated with the overridden bean instance.
*/ */
protected void injectFields(TestContext testContext) { protected void injectFields(TestContext testContext) {
postProcessFields(testContext, (testMetadata, overrideRegistrar) -> overrideRegistrar.inject( postProcessFields(testContext, (testMetadata, overrideRegistrar) -> overrideRegistrar.inject(
@ -64,9 +62,10 @@ public class BeanOverrideTestExecutionListener extends AbstractTestExecutionList
} }
/** /**
* Process the test instance and make sure that flagged fields for bean * Process the test instance and make sure that fields flagged for bean
* overriding are processed. If a fresh instance is required, the field * overriding are processed.
* is nulled out and then re-injected with the overridden bean instance. * <p>If a fresh instance is required, the field is nulled out and then
* re-injected with the overridden bean instance.
* <p>This method does nothing if the * <p>This method does nothing if the
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE} * {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE}
* attribute is not present in the {@code TestContext}. * attribute is not present in the {@code TestContext}.

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

@ -26,15 +26,15 @@ import org.springframework.core.style.ToStringCreator;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
/** /**
* Metadata for Bean Override injection points, also responsible for the * Metadata for Bean Override injection points, also responsible for creation of
* creation of the overriding instance. * the overriding instance.
* *
* <p><strong>WARNING</strong>: implementations are used as a cache key and * <p><strong>WARNING</strong>: implementations are used as a cache key and
* must implement proper {@code equals} and {@code hashCode}t methods. * must implement proper {@code equals()} and {@code hashCode()} methods.
* *
* <p>Specific implementations of metadata can have state to be used during * <p>Specific implementations of metadata can have state to be used during
* override {@linkplain #createOverride(String, BeanDefinition, Object) * override {@linkplain #createOverride(String, BeanDefinition, Object)
* instance creation} &mdash; for example, from further parsing of the * instance creation} &mdash; for example, based on further parsing of the
* annotation or the annotated field. * annotation or the annotated field.
* *
* @author Simon Baslé * @author Simon Baslé
@ -58,31 +58,32 @@ public abstract class OverrideMetadata {
} }
/** /**
* Return the bean name to override. * Get the bean name to override.
* <p>Defaults to the name of the {@link #getField() field}.
*/ */
protected String getBeanName() { protected String getBeanName() {
return this.field.getName(); return this.field.getName();
} }
/** /**
* Return the bean {@link ResolvableType type} to override. * Get the bean {@linkplain ResolvableType type} to override.
*/ */
public ResolvableType getBeanType() { public final ResolvableType getBeanType() {
return this.beanType; return this.beanType;
} }
/** /**
* Return the annotated {@link Field}. * Get the annotated {@link Field}.
*/ */
public Field getField() { public final Field getField() {
return this.field; return this.field;
} }
/** /**
* Return the {@link BeanOverrideStrategy} for this instance, as a hint on * Get the {@link BeanOverrideStrategy} for this instance, as a hint on
* how and when the override instance should be created. * how and when the override instance should be created.
*/ */
public BeanOverrideStrategy getStrategy() { public final BeanOverrideStrategy getStrategy() {
return this.strategy; return this.strategy;
} }
@ -91,9 +92,9 @@ public abstract class OverrideMetadata {
* optionally provided with an existing {@link BeanDefinition} and/or an * optionally provided with an existing {@link BeanDefinition} and/or an
* original instance, that is a singleton or an early wrapped instance. * original instance, that is a singleton or an early wrapped instance.
* @param beanName the name of the bean being overridden * @param beanName the name of the bean being overridden
* @param existingBeanDefinition an existing bean definition for that bean * @param existingBeanDefinition an existing bean definition for the supplied
* name, or {@code null} if not relevant * bean name, or {@code null} if not relevant
* @param existingBeanInstance an existing instance for that bean name, * @param existingBeanInstance an existing instance for the supplied bean name
* for wrapping purposes, or {@code null} if irrelevant * for wrapping purposes, or {@code null} if irrelevant
* @return the instance with which to override the bean * @return the instance with which to override the bean
*/ */

Loading…
Cancel
Save