Skip to content

Commit

Permalink
Reject static Bean Override fields for @⁠MockitoBean, @⁠TestBean, etc.
Browse files Browse the repository at this point in the history
Closes gh-33922
  • Loading branch information
sbrannen committed Nov 20, 2024
1 parent 8b66d3c commit 3569cfe
Show file tree
Hide file tree
Showing 9 changed files with 38 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
* <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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

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;
Expand Down Expand Up @@ -105,6 +106,8 @@ public static List<BeanOverrideHandler> forTestClass(Class<?> testClass) {
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");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ void forTestClassWithCompetingBeanOverrideAnnotationsOnSameField() {
.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"));
Expand Down Expand Up @@ -246,6 +254,12 @@ static String foo() {
}
}

static class StaticBeanOverrideField {

@DummyBean
static String message;
}

static class ConfigA {

ExampleService noQualifier;
Expand Down

0 comments on commit 3569cfe

Please sign in to comment.