Support @⁠MockitoBean and @⁠MockitoSpyBean on test constructor parameters

Prior to this commit, @⁠MockitoBean and @⁠MockitoSpyBean could be
declared on fields or at the type level (on test classes and test
interfaces), but not on constructor parameters. Consequently, a test
class could not use constructor injection for bean overrides.

To address that, this commit introduces support for @⁠MockitoBean and
@⁠MockitoSpyBean on constructor parameters in JUnit Jupiter test
classes. Specifically, the Bean Override infrastructure has been
overhauled to support constructor parameters as declaration sites and
injection points alongside fields, and the SpringExtension now
recognizes composed @⁠BeanOverride annotations on constructor
parameters in supportsParameter() and resolves them properly in
resolveParameter(). Note, however, that this support has not been
introduced for @⁠TestBean.

For example, the following which uses field injection:

   @⁠SpringJUnitConfig(TestConfig.class)
   class BeanOverrideTests {

      @⁠MockitoBean
      CustomService customService;

      // tests...
   }

Can now be rewritten to use constructor injection:

   @⁠SpringJUnitConfig(TestConfig.class)
   class BeanOverrideTests {

      private final CustomService customService;

      BeanOverrideTests(@⁠MockitoBean CustomService customService) {
         this.customService = customService;
      }

      // tests...
   }

With Kotlin this can be achieved even more succinctly via a compact
constructor declaration:

   @⁠SpringJUnitConfig(TestConfig::class)
   class BeanOverrideTests(@⁠MockitoBean val customService: CustomService) {

      // tests...
   }

Of course, if one is a fan of so-called "test records", that can also
be achieved succinctly with a Java record:

   @⁠SpringJUnitConfig(TestConfig.class)
   record BeanOverrideTests(@⁠MockitoBean CustomService customService) {

      // tests...
   }

Closes gh-36096
This commit is contained in:
Sam Brannen
2026-03-29 17:17:16 +02:00
parent 955f9d3ea9
commit f9523a785b
31 changed files with 1730 additions and 112 deletions
@@ -12,18 +12,20 @@ The annotations can be applied in the following ways.
* On a non-static field in a test class or any of its superclasses.
* On a non-static field in an enclosing class for a `@Nested` test class or in any class
in the type hierarchy or enclosing class hierarchy above the `@Nested` test class.
* On a parameter in the constructor for a test class.
* At the type level on a test class or any superclass or implemented interface in the
type hierarchy above the test class.
* At the type level on an enclosing class for a `@Nested` test class or on any class or
interface in the type hierarchy or enclosing class hierarchy above the `@Nested` test
class.
When `@MockitoBean` or `@MockitoSpyBean` is declared on a field, the bean to mock or spy
is inferred from the type of the annotated field. If multiple candidates exist in the
`ApplicationContext`, a `@Qualifier` annotation can be declared on the field to help
disambiguate. In the absence of a `@Qualifier` annotation, the name of the annotated
field will be used as a _fallback qualifier_. Alternatively, you can explicitly specify a
bean name to mock or spy by setting the `value` or `name` attribute in the annotation.
When `@MockitoBean` or `@MockitoSpyBean` is declared on a field or constructor parameter,
the bean to mock or spy is inferred from the type of the annotated field or parameter. If
multiple candidates exist in the `ApplicationContext`, a `@Qualifier` annotation can be
declared on the field or parameter to help disambiguate. In the absence of a `@Qualifier`
annotation, the name of the annotated field or parameter will be used as a _fallback
qualifier_. Alternatively, you can explicitly specify a bean name to mock or spy by
setting the `value` or `name` attribute in the annotation.
When `@MockitoBean` or `@MockitoSpyBean` is declared at the type level, the type of bean
(or beans) to mock or spy must be supplied via the `types` attribute in the annotation
@@ -201,6 +203,82 @@ Kotlin::
<1> Replace the bean named `service` with a Mockito mock.
======
The following example shows how to use `@MockitoBean` on a constructor parameter for a
by-type lookup.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
private final CustomService customService;
BeanOverrideTests(@MockitoBean CustomService customService) { // <1>
this.customService = customService;
}
// tests...
}
----
<1> Replace the bean with type `CustomService` with a Mockito mock and inject it into
the constructor.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig::class)
class BeanOverrideTests(@MockitoBean val customService: CustomService) { // <1>
// tests...
}
----
<1> Replace the bean with type `CustomService` with a Mockito mock and inject it into
the constructor.
======
The following example shows how to use `@MockitoBean` on a constructor parameter for a
by-name lookup.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
private final CustomService customService;
BeanOverrideTests(@MockitoBean("service") CustomService customService) { // <1>
this.customService = customService;
}
// tests...
}
----
<1> Replace the bean named `service` with a Mockito mock and inject it into the
constructor.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig::class)
class BeanOverrideTests(@MockitoBean("service") val customService: CustomService) { // <1>
// tests...
}
----
<1> Replace the bean named `service` with a Mockito mock and inject it into the
constructor.
======
The following `@SharedMocks` annotation registers two mocks by-type and one mock by-name.
[tabs]
@@ -375,6 +453,80 @@ Kotlin::
<1> Wrap the bean named `service` with a Mockito spy.
======
The following example shows how to use `@MockitoSpyBean` on a constructor parameter for
a by-type lookup.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
private final CustomService customService;
BeanOverrideTests(@MockitoSpyBean CustomService customService) { // <1>
this.customService = customService;
}
// tests...
}
----
<1> Wrap the bean with type `CustomService` with a Mockito spy and inject it into the
constructor.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig::class)
class BeanOverrideTests(@MockitoSpyBean val customService: CustomService) { // <1>
// tests...
}
----
<1> Wrap the bean with type `CustomService` with a Mockito spy and inject it into the
constructor.
======
The following example shows how to use `@MockitoSpyBean` on a constructor parameter for
a by-name lookup.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
private final CustomService customService;
BeanOverrideTests(@MockitoSpyBean("service") CustomService customService) { // <1>
this.customService = customService;
}
// tests...
}
----
<1> Wrap the bean named `service` with a Mockito spy and inject it into the constructor.
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig::class)
class BeanOverrideTests(@MockitoSpyBean("service") val customService: CustomService) { // <1>
// tests...
}
----
<1> Wrap the bean named `service` with a Mockito spy and inject it into the constructor.
======
The following `@SharedSpies` annotation registers two spies by-type and one spy by-name.
[tabs]
@@ -2,8 +2,9 @@
= 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 the test class or one or more
non-static fields in the test class.
`ApplicationContext` for a test class, by annotating the test class, one or more
non-static fields in the test class, or one or more parameters in the constructor for 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`
@@ -42,9 +43,9 @@ 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 for annotations on test classes as well as
annotations on non-static fields in test classes that are meta-annotated with
`@BeanOverride` and instantiates the corresponding `BeanOverrideProcessor` which is
The bean overriding infrastructure searches for annotations on test classes, non-static
fields in test classes, and parameters in test class constructors that are 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
@@ -179,6 +179,10 @@ If a specific parameter in a constructor for a JUnit Jupiter test class is of ty
`ApplicationContext` (or a sub-type thereof) or is annotated or meta-annotated with
`@Autowired`, `@Qualifier`, or `@Value`, Spring injects the value for that specific
parameter with the corresponding bean or value from the test's `ApplicationContext`.
Similarly, if a specific parameter is annotated with `@MockitoBean` or `@MockitoSpyBean`,
Spring will inject a Mockito mock or spy, respectively &mdash; see
xref:testing/annotations/integration-spring/annotation-mockitobean.adoc[`@MockitoBean` and `@MockitoSpyBean`]
for details.
Spring can also be configured to autowire all arguments for a test class constructor if
the constructor is considered to be _autowirable_. A constructor is considered to be