Reject static Bean Override fields for @MockitoBean, @TestBean, etc.
Closes gh-33922
This commit is contained in:
parent
8b66d3c79a
commit
3569cfe990
|
@ -1,9 +1,10 @@
|
||||||
[[spring-testing-annotation-beanoverriding-mockitobean]]
|
[[spring-testing-annotation-beanoverriding-mockitobean]]
|
||||||
= `@MockitoBean` and `@MockitoSpyBean`
|
= `@MockitoBean` and `@MockitoSpyBean`
|
||||||
|
|
||||||
`@MockitoBean` and `@MockitoSpyBean` are used on fields in test classes to override beans
|
`@MockitoBean` and `@MockitoSpyBean` are used on non-static fields in test classes to
|
||||||
in the test's `ApplicationContext` with a Mockito _mock_ or _spy_, respectively. In the
|
override beans in the test's `ApplicationContext` with a Mockito _mock_ or _spy_,
|
||||||
latter case, an early instance of the original bean is captured and wrapped by the 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.
|
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
|
If multiple candidates match, `@Qualifier` can be provided to narrow the candidate to
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
[[spring-testing-annotation-beanoverriding-testbean]]
|
[[spring-testing-annotation-beanoverriding-testbean]]
|
||||||
= `@TestBean`
|
= `@TestBean`
|
||||||
|
|
||||||
`@TestBean` is used on a field in a test class to override a specific bean in the test's
|
`@TestBean` is used on a non-static field in a test class to override a specific bean in
|
||||||
`ApplicationContext` with an instance provided by a factory method.
|
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
|
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
|
bean name if specified. The factory method must be `static`, accept no arguments, and
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
= Bean Overriding in Tests
|
= Bean Overriding in Tests
|
||||||
|
|
||||||
Bean overriding in tests refers to the ability to override specific beans in the
|
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
|
NOTE: This feature is intended as a less risky alternative to the practice of registering
|
||||||
a bean via `@Bean` with the `DefaultListableBeanFactory`
|
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`
|
{spring-framework-code}/spring-test/src/main/resources/META-INF/spring.factories[`META-INF/spring.factories`
|
||||||
properties file].
|
properties file].
|
||||||
|
|
||||||
The bean overriding infrastructure searches in test classes for any field meta-annotated
|
The bean overriding infrastructure searches in test classes for any non-static field that
|
||||||
with `@BeanOverride` and instantiates the corresponding `BeanOverrideProcessor` which is
|
is meta-annotated with `@BeanOverride` and instantiates the corresponding
|
||||||
responsible for creating an appropriate `BeanOverrideHandler`.
|
`BeanOverrideProcessor` which is responsible for creating an appropriate
|
||||||
|
`BeanOverrideHandler`.
|
||||||
|
|
||||||
The internal `BeanOverrideBeanFactoryPostProcessor` then uses bean override handlers to
|
The internal `BeanOverrideBeanFactoryPostProcessor` then uses bean override handlers to
|
||||||
alter the test's `ApplicationContext` by creating, replacing, or wrapping beans as
|
alter the test's `ApplicationContext` by creating, replacing, or wrapping beans as
|
||||||
|
|
|
@ -30,7 +30,7 @@ import org.springframework.aot.hint.annotation.Reflective;
|
||||||
* <p>Specifying this annotation registers the configured {@link BeanOverrideProcessor}
|
* <p>Specifying this annotation registers 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>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)}.
|
* expected that it is meta-annotated with {@link Target @Target(ElementType.FIELD)}.
|
||||||
*
|
*
|
||||||
* <p>For concrete examples, see
|
* <p>For concrete examples, see
|
||||||
|
|
|
@ -18,6 +18,7 @@ package org.springframework.test.context.bean.override;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -105,6 +106,8 @@ public abstract class BeanOverrideHandler {
|
||||||
private static void processField(Field field, Class<?> testClass, List<BeanOverrideHandler> handlers) {
|
private static void processField(Field field, Class<?> testClass, List<BeanOverrideHandler> handlers) {
|
||||||
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(!Modifier.isStatic(field.getModifiers()),
|
||||||
|
() -> "@BeanOverride field must not be static: " + field);
|
||||||
MergedAnnotation<?> metaSource = mergedAnnotation.getMetaSource();
|
MergedAnnotation<?> metaSource = mergedAnnotation.getMetaSource();
|
||||||
Assert.state(metaSource != null, "@BeanOverride annotation must be meta-present");
|
Assert.state(metaSource != null, "@BeanOverride annotation must be meta-present");
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,8 @@ import org.springframework.core.annotation.AliasFor;
|
||||||
import org.springframework.test.context.bean.override.BeanOverride;
|
import org.springframework.test.context.bean.override.BeanOverride;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code @TestBean} is an annotation that can be applied to a field in a test
|
* {@code @TestBean} is an annotation that can be applied to a non-static field
|
||||||
* class to override a bean in the test's
|
* in a test class to override a bean in the test's
|
||||||
* {@link org.springframework.context.ApplicationContext ApplicationContext}
|
* {@link org.springframework.context.ApplicationContext ApplicationContext}
|
||||||
* using a static factory method.
|
* using a static factory method.
|
||||||
*
|
*
|
||||||
|
|
|
@ -29,8 +29,8 @@ import org.springframework.core.annotation.AliasFor;
|
||||||
import org.springframework.test.context.bean.override.BeanOverride;
|
import org.springframework.test.context.bean.override.BeanOverride;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code @MockitoBean} is an annotation that can be applied to a field in a test
|
* {@code @MockitoBean} is an annotation that can be applied to a non-static field
|
||||||
* class to override a bean in the test's
|
* in a test class to override a bean in the test's
|
||||||
* {@link org.springframework.context.ApplicationContext ApplicationContext}
|
* {@link org.springframework.context.ApplicationContext ApplicationContext}
|
||||||
* using a Mockito mock.
|
* using a Mockito mock.
|
||||||
*
|
*
|
||||||
|
|
|
@ -26,8 +26,10 @@ import org.springframework.core.annotation.AliasFor;
|
||||||
import org.springframework.test.context.bean.override.BeanOverride;
|
import org.springframework.test.context.bean.override.BeanOverride;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark a field to trigger a bean override using a Mockito spy, which will wrap
|
* {@code @MockitoSpyBean} is an annotation that can be applied to a non-static
|
||||||
* the original bean instance.
|
* 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
|
* <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
|
* field. If multiple candidates exist, a {@code @Qualifier} annotation can be
|
||||||
|
|
|
@ -86,6 +86,14 @@ class BeanOverrideHandlerTests {
|
||||||
.withMessageContaining(faultyField.toString());
|
.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
|
@Test
|
||||||
void getBeanNameIsNullByDefault() {
|
void getBeanNameIsNullByDefault() {
|
||||||
BeanOverrideHandler handler = createBeanOverrideHandler(field(ConfigA.class, "noQualifier"));
|
BeanOverrideHandler handler = createBeanOverrideHandler(field(ConfigA.class, "noQualifier"));
|
||||||
|
@ -246,6 +254,12 @@ class BeanOverrideHandlerTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class StaticBeanOverrideField {
|
||||||
|
|
||||||
|
@DummyBean
|
||||||
|
static String message;
|
||||||
|
}
|
||||||
|
|
||||||
static class ConfigA {
|
static class ConfigA {
|
||||||
|
|
||||||
ExampleService noQualifier;
|
ExampleService noQualifier;
|
||||||
|
|
Loading…
Reference in New Issue