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 index 2056f6454f..6aa5ddd9c2 100644 --- 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 @@ -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 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 index 6da8abed74..f814e50c00 100644 --- 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 @@ -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 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 index b35ee1dd3a..52edc4a599 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/bean-overriding.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/bean-overriding.adoc @@ -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 diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverride.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverride.java index 8aaceb8529..910eb50195 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverride.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverride.java @@ -30,7 +30,7 @@ import org.springframework.aot.hint.annotation.Reflective; *

Specifying this annotation registers the configured {@link BeanOverrideProcessor} * which must be capable of handling the composed annotation and its attributes. * - *

Since the composed annotation should only be applied to fields, it is + *

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)}. * *

For concrete examples, see diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideHandler.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideHandler.java index 79bcdaea7f..b0f6c0f56d 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideHandler.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideHandler.java @@ -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 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"); diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBean.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBean.java index c2bd8925be..b4c11f8b4a 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBean.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBean.java @@ -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. * diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBean.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBean.java index d664217c49..111f07638a 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBean.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBean.java @@ -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. * diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBean.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBean.java index 359579d639..b465347b0d 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBean.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBean.java @@ -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. * *

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 diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideHandlerTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideHandlerTests.java index 85b4d0df2f..1a64107989 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideHandlerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideHandlerTests.java @@ -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;