Skip to content

Commit f9523a7

Browse files
committed
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
1 parent 955f9d3 commit f9523a7

31 files changed

Lines changed: 1730 additions & 112 deletions

File tree

framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-mockitobean.adoc

Lines changed: 158 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,20 @@ The annotations can be applied in the following ways.
1212
* On a non-static field in a test class or any of its superclasses.
1313
* On a non-static field in an enclosing class for a `@Nested` test class or in any class
1414
in the type hierarchy or enclosing class hierarchy above the `@Nested` test class.
15+
* On a parameter in the constructor for a test class.
1516
* At the type level on a test class or any superclass or implemented interface in the
1617
type hierarchy above the test class.
1718
* At the type level on an enclosing class for a `@Nested` test class or on any class or
1819
interface in the type hierarchy or enclosing class hierarchy above the `@Nested` test
1920
class.
2021

21-
When `@MockitoBean` or `@MockitoSpyBean` is declared on a field, the bean to mock or spy
22-
is inferred from the type of the annotated field. If multiple candidates exist in the
23-
`ApplicationContext`, a `@Qualifier` annotation can be declared on the field to help
24-
disambiguate. In the absence of a `@Qualifier` annotation, the name of the annotated
25-
field will be used as a _fallback qualifier_. Alternatively, you can explicitly specify a
26-
bean name to mock or spy by setting the `value` or `name` attribute in the annotation.
22+
When `@MockitoBean` or `@MockitoSpyBean` is declared on a field or constructor parameter,
23+
the bean to mock or spy is inferred from the type of the annotated field or parameter. If
24+
multiple candidates exist in the `ApplicationContext`, a `@Qualifier` annotation can be
25+
declared on the field or parameter to help disambiguate. In the absence of a `@Qualifier`
26+
annotation, the name of the annotated field or parameter will be used as a _fallback
27+
qualifier_. Alternatively, you can explicitly specify a bean name to mock or spy by
28+
setting the `value` or `name` attribute in the annotation.
2729

2830
When `@MockitoBean` or `@MockitoSpyBean` is declared at the type level, the type of bean
2931
(or beans) to mock or spy must be supplied via the `types` attribute in the annotation –
@@ -201,6 +203,82 @@ Kotlin::
201203
<1> Replace the bean named `service` with a Mockito mock.
202204
======
203205

206+
The following example shows how to use `@MockitoBean` on a constructor parameter for a
207+
by-type lookup.
208+
209+
[tabs]
210+
======
211+
Java::
212+
+
213+
[source,java,indent=0,subs="verbatim,quotes"]
214+
----
215+
@SpringJUnitConfig(TestConfig.class)
216+
class BeanOverrideTests {
217+
218+
private final CustomService customService;
219+
220+
BeanOverrideTests(@MockitoBean CustomService customService) { // <1>
221+
this.customService = customService;
222+
}
223+
224+
// tests...
225+
}
226+
----
227+
<1> Replace the bean with type `CustomService` with a Mockito mock and inject it into
228+
the constructor.
229+
230+
Kotlin::
231+
+
232+
[source,kotlin,indent=0,subs="verbatim,quotes"]
233+
----
234+
@SpringJUnitConfig(TestConfig::class)
235+
class BeanOverrideTests(@MockitoBean val customService: CustomService) { // <1>
236+
237+
// tests...
238+
}
239+
----
240+
<1> Replace the bean with type `CustomService` with a Mockito mock and inject it into
241+
the constructor.
242+
======
243+
244+
The following example shows how to use `@MockitoBean` on a constructor parameter for a
245+
by-name lookup.
246+
247+
[tabs]
248+
======
249+
Java::
250+
+
251+
[source,java,indent=0,subs="verbatim,quotes"]
252+
----
253+
@SpringJUnitConfig(TestConfig.class)
254+
class BeanOverrideTests {
255+
256+
private final CustomService customService;
257+
258+
BeanOverrideTests(@MockitoBean("service") CustomService customService) { // <1>
259+
this.customService = customService;
260+
}
261+
262+
// tests...
263+
}
264+
----
265+
<1> Replace the bean named `service` with a Mockito mock and inject it into the
266+
constructor.
267+
268+
Kotlin::
269+
+
270+
[source,kotlin,indent=0,subs="verbatim,quotes"]
271+
----
272+
@SpringJUnitConfig(TestConfig::class)
273+
class BeanOverrideTests(@MockitoBean("service") val customService: CustomService) { // <1>
274+
275+
// tests...
276+
}
277+
----
278+
<1> Replace the bean named `service` with a Mockito mock and inject it into the
279+
constructor.
280+
======
281+
204282
The following `@SharedMocks` annotation registers two mocks by-type and one mock by-name.
205283

206284
[tabs]
@@ -375,6 +453,80 @@ Kotlin::
375453
<1> Wrap the bean named `service` with a Mockito spy.
376454
======
377455

456+
The following example shows how to use `@MockitoSpyBean` on a constructor parameter for
457+
a by-type lookup.
458+
459+
[tabs]
460+
======
461+
Java::
462+
+
463+
[source,java,indent=0,subs="verbatim,quotes"]
464+
----
465+
@SpringJUnitConfig(TestConfig.class)
466+
class BeanOverrideTests {
467+
468+
private final CustomService customService;
469+
470+
BeanOverrideTests(@MockitoSpyBean CustomService customService) { // <1>
471+
this.customService = customService;
472+
}
473+
474+
// tests...
475+
}
476+
----
477+
<1> Wrap the bean with type `CustomService` with a Mockito spy and inject it into the
478+
constructor.
479+
480+
Kotlin::
481+
+
482+
[source,kotlin,indent=0,subs="verbatim,quotes"]
483+
----
484+
@SpringJUnitConfig(TestConfig::class)
485+
class BeanOverrideTests(@MockitoSpyBean val customService: CustomService) { // <1>
486+
487+
// tests...
488+
}
489+
----
490+
<1> Wrap the bean with type `CustomService` with a Mockito spy and inject it into the
491+
constructor.
492+
======
493+
494+
The following example shows how to use `@MockitoSpyBean` on a constructor parameter for
495+
a by-name lookup.
496+
497+
[tabs]
498+
======
499+
Java::
500+
+
501+
[source,java,indent=0,subs="verbatim,quotes"]
502+
----
503+
@SpringJUnitConfig(TestConfig.class)
504+
class BeanOverrideTests {
505+
506+
private final CustomService customService;
507+
508+
BeanOverrideTests(@MockitoSpyBean("service") CustomService customService) { // <1>
509+
this.customService = customService;
510+
}
511+
512+
// tests...
513+
}
514+
----
515+
<1> Wrap the bean named `service` with a Mockito spy and inject it into the constructor.
516+
517+
Kotlin::
518+
+
519+
[source,kotlin,indent=0,subs="verbatim,quotes"]
520+
----
521+
@SpringJUnitConfig(TestConfig::class)
522+
class BeanOverrideTests(@MockitoSpyBean("service") val customService: CustomService) { // <1>
523+
524+
// tests...
525+
}
526+
----
527+
<1> Wrap the bean named `service` with a Mockito spy and inject it into the constructor.
528+
======
529+
378530
The following `@SharedSpies` annotation registers two spies by-type and one spy by-name.
379531

380532
[tabs]

framework-docs/modules/ROOT/pages/testing/testcontext-framework/bean-overriding.adoc

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
= Bean Overriding in Tests
33

44
Bean overriding in tests refers to the ability to override specific beans in the
5-
`ApplicationContext` for a test class, by annotating the test class or one or more
6-
non-static fields in the test class.
5+
`ApplicationContext` for a test class, by annotating the test class, one or more
6+
non-static fields in the test class, or one or more parameters in the constructor for the
7+
test class.
78

89
NOTE: This feature is intended as a less risky alternative to the practice of registering
910
a bean via `@Bean` with the `DefaultListableBeanFactory`
@@ -42,9 +43,9 @@ The `spring-test` module registers implementations of the latter two
4243
{spring-framework-code}/spring-test/src/main/resources/META-INF/spring.factories[`META-INF/spring.factories`
4344
properties file].
4445

45-
The bean overriding infrastructure searches for annotations on test classes as well as
46-
annotations on non-static fields in test classes that are meta-annotated with
47-
`@BeanOverride` and instantiates the corresponding `BeanOverrideProcessor` which is
46+
The bean overriding infrastructure searches for annotations on test classes, non-static
47+
fields in test classes, and parameters in test class constructors that are meta-annotated
48+
with `@BeanOverride`, and instantiates the corresponding `BeanOverrideProcessor` which is
4849
responsible for creating an appropriate `BeanOverrideHandler`.
4950

5051
The internal `BeanOverrideBeanFactoryPostProcessor` then uses bean override handlers to

framework-docs/modules/ROOT/pages/testing/testcontext-framework/support-classes.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,10 @@ If a specific parameter in a constructor for a JUnit Jupiter test class is of ty
179179
`ApplicationContext` (or a sub-type thereof) or is annotated or meta-annotated with
180180
`@Autowired`, `@Qualifier`, or `@Value`, Spring injects the value for that specific
181181
parameter with the corresponding bean or value from the test's `ApplicationContext`.
182+
Similarly, if a specific parameter is annotated with `@MockitoBean` or `@MockitoSpyBean`,
183+
Spring will inject a Mockito mock or spy, respectively &mdash; see
184+
xref:testing/annotations/integration-spring/annotation-mockitobean.adoc[`@MockitoBean` and `@MockitoSpyBean`]
185+
for details.
182186

183187
Spring can also be configured to autowire all arguments for a test class constructor if
184188
the constructor is considered to be _autowirable_. A constructor is considered to be

spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverride.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@
3434
* fields, it is expected that the composed annotation is meta-annotated with
3535
* {@link Target @Target(ElementType.FIELD)}. However, certain bean override
3636
* annotations may be declared with an additional {@code ElementType.TYPE} target
37-
* for use at the type level, as is the case for {@code @MockitoBean} which can
38-
* be declared on a field, test class, or test interface.
37+
* for use at the type level. Similarly, as of Spring Framework 7.1, certain bean
38+
* override annotations may be declared with an additional {@code ElementType.PARAMETER}
39+
* target for use on constructor parameters. For example, {@code @MockitoBean} can
40+
* be declared on a field, constructor parameter, test class, or test interface.
3941
*
4042
* <p>For concrete examples of such composed annotations, see
4143
* {@link org.springframework.test.context.bean.override.convention.TestBean @TestBean},

0 commit comments

Comments
 (0)