Reject static Bean Override fields for @⁠MockitoBean, @⁠TestBean, etc.

Closes gh-33922
This commit is contained in:
Sam Brannen 2024-11-20 11:22:26 +01:00
parent 8b66d3c79a
commit 3569cfe990
9 changed files with 38 additions and 16 deletions

View File

@ -1,9 +1,10 @@
[[spring-testing-annotation-beanoverriding-mockitobean]]
= `@MockitoBean` and `@MockitoSpyBean`
`@MockitoBean` and `@MockitoSpyBean` are used on fields in test classes to override beans
in the test's `ApplicationContext` with a Mockito _mock_ or _spy_, respectively. In the
latter case, an early instance of the original bean is captured and wrapped by the spy.
`@MockitoBean` and `@MockitoSpyBean` are used on non-static fields in test classes to
override beans in the test's `ApplicationContext` with a Mockito _mock_ or _spy_,
respectively. In the latter case, an early instance of the original bean is captured and
wrapped by the spy.
By default, the annotated field's type is used to search for candidate beans to override.
If multiple candidates match, `@Qualifier` can be provided to narrow the candidate to

View File

@ -1,8 +1,8 @@
[[spring-testing-annotation-beanoverriding-testbean]]
= `@TestBean`
`@TestBean` is used on a field in a test class to override a specific bean in the test's
`ApplicationContext` with an instance provided by a factory method.
`@TestBean` is used on a non-static field in a test class to override a specific bean in
the test's `ApplicationContext` with an instance provided by a factory method.
The associated factory method name is derived from the annotated field's name, or the
bean name if specified. The factory method must be `static`, accept no arguments, and

View File

@ -2,7 +2,8 @@
= Bean Overriding in Tests
Bean overriding in tests refers to the ability to override specific beans in the
`ApplicationContext` for a test class, by annotating one or more fields in the test class.
`ApplicationContext` for a test class, by annotating one or more non-static fields in the
test class.
NOTE: This feature is intended as a less risky alternative to the practice of registering
a bean via `@Bean` with the `DefaultListableBeanFactory`
@ -41,9 +42,10 @@ The `spring-test` module registers implementations of the latter two
{spring-framework-code}/spring-test/src/main/resources/META-INF/spring.factories[`META-INF/spring.factories`
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 creating an appropriate `BeanOverrideHandler`.
The bean overriding infrastructure searches in test classes for any non-static field that
is meta-annotated with `@BeanOverride` and instantiates the corresponding
`BeanOverrideProcessor` which is responsible for creating an appropriate
`BeanOverrideHandler`.
The internal `BeanOverrideBeanFactoryPostProcessor` then uses bean override handlers to
alter the test's `ApplicationContext` by creating, replacing, or wrapping beans as

View File

@ -30,7 +30,7 @@ import org.springframework.aot.hint.annotation.Reflective;
* <p>Specifying this annotation registers the configured {@link BeanOverrideProcessor}
* which must be capable of handling the composed annotation and its attributes.
*
* <p>Since the composed annotation should only be applied to fields, it is
* <p>Since the composed annotation should only be applied to non-static fields, it is
* expected that it is meta-annotated with {@link Target @Target(ElementType.FIELD)}.
*
* <p>For concrete examples, see

View File

@ -18,6 +18,7 @@ package org.springframework.test.context.bean.override;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
@ -105,6 +106,8 @@ public abstract class BeanOverrideHandler {
private static void processField(Field field, Class<?> testClass, List<BeanOverrideHandler> handlers) {
AtomicBoolean overrideAnnotationFound = new AtomicBoolean();
MergedAnnotations.from(field, DIRECT).stream(BeanOverride.class).forEach(mergedAnnotation -> {
Assert.state(!Modifier.isStatic(field.getModifiers()),
() -> "@BeanOverride field must not be static: " + field);
MergedAnnotation<?> metaSource = mergedAnnotation.getMetaSource();
Assert.state(metaSource != null, "@BeanOverride annotation must be meta-present");

View File

@ -27,8 +27,8 @@ import org.springframework.core.annotation.AliasFor;
import org.springframework.test.context.bean.override.BeanOverride;
/**
* {@code @TestBean} is an annotation that can be applied to a field in a test
* class to override a bean in the test's
* {@code @TestBean} is an annotation that can be applied to a non-static field
* in a test class to override a bean in the test's
* {@link org.springframework.context.ApplicationContext ApplicationContext}
* using a static factory method.
*

View File

@ -29,8 +29,8 @@ import org.springframework.core.annotation.AliasFor;
import org.springframework.test.context.bean.override.BeanOverride;
/**
* {@code @MockitoBean} is an annotation that can be applied to a field in a test
* class to override a bean in the test's
* {@code @MockitoBean} is an annotation that can be applied to a non-static field
* in a test class to override a bean in the test's
* {@link org.springframework.context.ApplicationContext ApplicationContext}
* using a Mockito mock.
*

View File

@ -26,8 +26,10 @@ import org.springframework.core.annotation.AliasFor;
import org.springframework.test.context.bean.override.BeanOverride;
/**
* Mark a field to trigger a bean override using a Mockito spy, which will wrap
* the original bean instance.
* {@code @MockitoSpyBean} is an annotation that can be applied to a non-static
* field in a test class to override a bean in the test's
* {@link org.springframework.context.ApplicationContext ApplicationContext}
* with a Mockito spy that wraps the original bean instance.
*
* <p>By default, the bean to spy is inferred from the type of the annotated
* field. If multiple candidates exist, a {@code @Qualifier} annotation can be

View File

@ -86,6 +86,14 @@ class BeanOverrideHandlerTests {
.withMessageContaining(faultyField.toString());
}
@Test // gh-33922
void forTestClassWithStaticBeanOverrideField() {
Field staticField = field(StaticBeanOverrideField.class, "message");
assertThatIllegalStateException()
.isThrownBy(() -> BeanOverrideHandler.forTestClass(StaticBeanOverrideField.class))
.withMessage("@BeanOverride field must not be static: " + staticField);
}
@Test
void getBeanNameIsNullByDefault() {
BeanOverrideHandler handler = createBeanOverrideHandler(field(ConfigA.class, "noQualifier"));
@ -246,6 +254,12 @@ class BeanOverrideHandlerTests {
}
}
static class StaticBeanOverrideField {
@DummyBean
static String message;
}
static class ConfigA {
ExampleService noQualifier;