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 6bb17af198d4..8af25b975518 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,34 +1,60 @@
[[spring-testing-annotation-beanoverriding-mockitobean]]
= `@MockitoBean` and `@MockitoSpyBean`
-`@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.
+`@MockitoBean` and `@MockitoSpyBean` can be used in test classes to override a bean 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.
+
+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.
+* 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 at the type level, the type of bean
+(or beans) to mock or spy must be supplied via the `types` attribute in the annotation –
+for example, `@MockitoBean(types = {OrderService.class, UserService.class})`. If multiple
+candidates exist in the `ApplicationContext`, you can explicitly specify a bean name to
+mock or spy by setting the `name` attribute. Note, however, that the `types` attribute
+must contain a single type if an explicit bean `name` is configured – for example,
+`@MockitoBean(name = "ps1", types = PrintingService.class)`.
-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
-override. Alternatively, a candidate whose bean name matches the name of the field will
-match.
+To support reuse of mock configuration, `@MockitoBean` and `@MockitoSpyBean` may be used
+as meta-annotations to create custom _composed annotations_ – for example, to define
+common mock or spy configuration in a single annotation that can be reused across a test
+suite. `@MockitoBean` and `@MockitoSpyBean` can also be used as repeatable annotations at
+the type level — for example, to mock or spy several beans by name.
[WARNING]
====
-Qualifiers, including the name of the field, are used to determine if a separate
+Qualifiers, including the name of a field, are used to determine if a separate
`ApplicationContext` needs to be created. If you are using this feature to mock or spy
-the same bean in several test classes, make sure to name the field consistently to avoid
+the same bean in several test classes, make sure to name the fields consistently to avoid
creating unnecessary contexts.
====
Each annotation also defines Mockito-specific attributes to fine-tune the mocking behavior.
The `@MockitoBean` annotation uses the `REPLACE_OR_CREATE`
-xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy for test bean overriding].
-If no existing bean matches, a new bean is created on the fly. However, you can switch to
-the `REPLACE` strategy by setting the `enforceOverride` attribute to `true`. See the
-following section for an example.
+xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-strategy[strategy for bean overrides].
+If a corresponding bean does not exist, a new bean will be created. However, you can
+switch to the `REPLACE` strategy by setting the `enforceOverride` attribute to `true` –
+for example, `@MockitoBean(enforceOverride = true)`.
The `@MockitoSpyBean` annotation uses the `WRAP`
-xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy],
+xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-strategy[strategy],
and the original instance is wrapped in a Mockito spy. This strategy requires that
exactly one candidate bean exists.
@@ -56,15 +82,8 @@ or `private` depending on the needs or coding practices of the project.
[[spring-testing-annotation-beanoverriding-mockitobean-examples]]
== `@MockitoBean` Examples
-When using `@MockitoBean`, a new bean will be created if a corresponding bean does not
-exist. However, if you would like for the test to fail when a corresponding bean does not
-exist, you can set the `enforceOverride` attribute to `true` – for example,
-`@MockitoBean(enforceOverride = true)`.
-
-To use a by-name override rather than a by-type override, specify the `name` (or `value`)
-attribute of the annotation.
-
-The following example shows how to use the default behavior of the `@MockitoBean` annotation:
+The following example shows how to use the default behavior of the `@MockitoBean`
+annotation.
[tabs]
======
@@ -81,7 +100,7 @@ Java::
// tests...
}
----
-<1> Replace the bean with type `CustomService` with a Mockito `mock`.
+<1> Replace the bean with type `CustomService` with a Mockito mock.
======
In the example above, we are creating a mock for `CustomService`. If more than one bean
@@ -90,7 +109,8 @@ will fail, and you will need to provide a qualifier of some sort to identify whi
`CustomService` beans you want to override. If no such bean exists, a bean will be
created with an auto-generated bean name.
-The following example uses a by-name lookup, rather than a by-type lookup:
+The following example uses a by-name lookup, rather than a by-type lookup. If no bean
+named `service` exists, one is created.
[tabs]
======
@@ -108,32 +128,9 @@ Java::
}
----
-<1> Replace the bean named `service` with a Mockito `mock`.
+<1> Replace the bean named `service` with a Mockito mock.
======
-If no bean named `service` exists, one is created.
-
-`@MockitoBean` can also be used at the type level:
-
-- on a test class or any superclass or implemented interface in the type hierarchy above
- the test class
-- 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` is declared at the type level, the type of bean (or beans) to mock
-must be supplied via the `types` attribute – for example,
-`@MockitoBean(types = {OrderService.class, UserService.class})`. If multiple candidates
-exist in the application context, you can explicitly specify a bean name to mock by
-setting the `name` attribute. Note, however, that the `types` attribute must contain a
-single type if an explicit bean `name` is configured – for example,
-`@MockitoBean(name = "ps1", types = PrintingService.class)`.
-
-To support reuse of mock configuration, `@MockitoBean` may be used as a meta-annotation
-to create custom _composed annotations_ — for example, to define common mock
-configuration in a single annotation that can be reused across a test suite.
-`@MockitoBean` can also be used as a repeatable annotation at the type level — for
-example, to mock several beans by name.
-
The following `@SharedMocks` annotation registers two mocks by-type and one mock by-name.
[tabs]
@@ -191,7 +188,7 @@ APIs.
== `@MockitoSpyBean` Examples
The following example shows how to use the default behavior of the `@MockitoSpyBean`
-annotation:
+annotation.
[tabs]
======
@@ -208,7 +205,7 @@ Java::
// tests...
}
----
-<1> Wrap the bean with type `CustomService` with a Mockito `spy`.
+<1> Wrap the bean with type `CustomService` with a Mockito spy.
======
In the example above, we are wrapping the bean with type `CustomService`. If more than
@@ -216,7 +213,7 @@ one bean of that type exists, the bean named `customService` is considered. Othe
the test will fail, and you will need to provide a qualifier of some sort to identify
which of the `CustomService` beans you want to spy.
-The following example uses a by-name lookup, rather than a by-type lookup:
+The following example uses a by-name lookup, rather than a by-type lookup.
[tabs]
======
@@ -233,5 +230,58 @@ Java::
// tests...
}
----
-<1> Wrap the bean named `service` with a Mockito `spy`.
+<1> Wrap the bean named `service` with a Mockito spy.
+======
+
+The following `@SharedSpies` annotation registers two spies by-type and one spy by-name.
+
+[tabs]
+======
+Java::
++
+[source,java,indent=0,subs="verbatim,quotes"]
+----
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ @MockitoSpyBean(types = {OrderService.class, UserService.class}) // <1>
+ @MockitoSpyBean(name = "ps1", types = PrintingService.class) // <2>
+ public @interface SharedSpies {
+ }
+----
+<1> Register `OrderService` and `UserService` spies by-type.
+<2> Register `PrintingService` spy by-name.
+======
+
+The following demonstrates how `@SharedSpies` can be used on a test class.
+
+[tabs]
+======
+Java::
++
+[source,java,indent=0,subs="verbatim,quotes"]
+----
+ @SpringJUnitConfig(TestConfig.class)
+ @SharedSpies // <1>
+ class BeanOverrideTests {
+
+ @Autowired OrderService orderService; // <2>
+
+ @Autowired UserService userService; // <2>
+
+ @Autowired PrintingService ps1; // <2>
+
+ // Inject other components that rely on the spies.
+
+ @Test
+ void testThatDependsOnMocks() {
+ // ...
+ }
+ }
+----
+<1> Register common spies via the custom `@SharedSpies` annotation.
+<2> Optionally inject spies to _stub_ or _verify_ them.
======
+
+TIP: The spies can also be injected into `@Configuration` classes or other test-related
+components in the `ApplicationContext` in order to configure them with Mockito's stubbing
+APIs.
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 52edc4a59947..a709dd96e432 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,8 +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 non-static fields in the
-test class.
+`ApplicationContext` for a test class, by annotating the test class or 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`
@@ -42,15 +42,16 @@ 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 non-static field that
-is meta-annotated with `@BeanOverride` and instantiates the corresponding
-`BeanOverrideProcessor` which is responsible for creating an appropriate
-`BeanOverrideHandler`.
+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
+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
defined by the corresponding `BeanOverrideStrategy`:
+[[testcontext-bean-overriding-strategy]]
`REPLACE`::
Replaces the bean. Throws an exception if a corresponding bean does not exist.
`REPLACE_OR_CREATE`::
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 7d11bd813c22..46d5c0917f9c 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
@@ -31,9 +31,9 @@
/**
* {@code @MockitoBean} is an annotation that can be used in test classes to
- * override beans in a test's
+ * override a bean in the test's
* {@link org.springframework.context.ApplicationContext ApplicationContext}
- * using Mockito mocks.
+ * with a Mockito mock.
*
*
{@code @MockitoBean} can be applied in the following ways.
*
*
* When {@code @MockitoBean} is declared on a field, the bean to mock is inferred
- * from the type of the annotated field. If multiple candidates exist, a
- * {@code @Qualifier} annotation can be declared on the field to help disambiguate.
- * In the absence of a {@code @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 by setting the {@link #value() value} or
- * {@link #name() name} attribute.
+ * from the type of the annotated field. If multiple candidates exist in the
+ * {@code ApplicationContext}, a {@code @Qualifier} annotation can be declared
+ * on the field to help disambiguate. In the absence of a {@code @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
+ * by setting the {@link #value() value} or {@link #name() name} attribute.
*
*
When {@code @MockitoBean} is declared at the type level, the type of bean
- * to mock must be supplied via the {@link #types() types} attribute. If multiple
- * candidates exist, you can explicitly specify a bean name to mock by setting the
- * {@link #name() name} attribute. Note, however, that the {@code types} attribute
- * must contain a single type if an explicit bean {@code name} is configured.
+ * (or beans) to mock must be supplied via the {@link #types() types} attribute.
+ * If multiple candidates exist in the {@code ApplicationContext}, you can
+ * explicitly specify a bean name to mock by setting the {@link #name() name}
+ * attribute. Note, however, that the {@code types} attribute must contain a
+ * single type if an explicit bean {@code name} is configured.
*
*
A bean will be created if a corresponding bean does not exist. However, if
* you would like for the test to fail when a corresponding bean does not exist,
@@ -111,7 +112,7 @@
public @interface MockitoBean {
/**
- * Alias for {@link #name()}.
+ * Alias for {@link #name() name}.
*
Intended to be used when no other attributes are needed — for
* example, {@code @MockitoBean("customBeanName")}.
* @see #name()
@@ -136,7 +137,7 @@
*
Each type specified will result in a mock being created and registered
* with the {@code ApplicationContext}.
*
Types must be omitted when the annotation is used on a field.
- *
When {@code @MockitoBean} also defines a {@link #name}, this attribute
+ *
When {@code @MockitoBean} also defines a {@link #name name}, this attribute
* can only contain a single value.
* @return the types to mock
* @since 6.2.2
diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessor.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessor.java
index 62c07d594153..7756dd720bdb 100644
--- a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessor.java
+++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessor.java
@@ -45,8 +45,10 @@ public AbstractMockitoBeanOverrideHandler createHandler(Annotation overrideAnnot
"The @MockitoBean 'types' attribute must be omitted when declared on a field");
return new MockitoBeanOverrideHandler(field, ResolvableType.forField(field, testClass), mockitoBean);
}
- else if (overrideAnnotation instanceof MockitoSpyBean spyBean) {
- return new MockitoSpyBeanOverrideHandler(field, ResolvableType.forField(field, testClass), spyBean);
+ else if (overrideAnnotation instanceof MockitoSpyBean mockitoSpyBean) {
+ Assert.state(mockitoSpyBean.types().length == 0,
+ "The @MockitoSpyBean 'types' attribute must be omitted when declared on a field");
+ return new MockitoSpyBeanOverrideHandler(field, ResolvableType.forField(field, testClass), mockitoSpyBean);
}
throw new IllegalStateException("""
Invalid annotation passed to MockitoBeanOverrideProcessor: \
@@ -56,21 +58,34 @@ else if (overrideAnnotation instanceof MockitoSpyBean spyBean) {
@Override
public List createHandlers(Annotation overrideAnnotation, Class> testClass) {
- if (!(overrideAnnotation instanceof MockitoBean mockitoBean)) {
- throw new IllegalStateException("""
- Invalid annotation passed to MockitoBeanOverrideProcessor: \
- expected @MockitoBean on test class """ + testClass.getName());
+ if (overrideAnnotation instanceof MockitoBean mockitoBean) {
+ Class>[] types = mockitoBean.types();
+ Assert.state(types.length > 0,
+ "The @MockitoBean 'types' attribute must not be empty when declared on a class");
+ Assert.state(mockitoBean.name().isEmpty() || types.length == 1,
+ "The @MockitoBean 'name' attribute cannot be used when mocking multiple types");
+ List handlers = new ArrayList<>();
+ for (Class> type : types) {
+ handlers.add(new MockitoBeanOverrideHandler(ResolvableType.forClass(type), mockitoBean));
+ }
+ return handlers;
}
- Class>[] types = mockitoBean.types();
- Assert.state(types.length > 0,
- "The @MockitoBean 'types' attribute must not be empty when declared on a class");
- Assert.state(mockitoBean.name().isEmpty() || types.length == 1,
- "The @MockitoBean 'name' attribute cannot be used when mocking multiple types");
- List handlers = new ArrayList<>();
- for (Class> type : types) {
- handlers.add(new MockitoBeanOverrideHandler(ResolvableType.forClass(type), mockitoBean));
+ else if (overrideAnnotation instanceof MockitoSpyBean mockitoSpyBean) {
+ Class>[] types = mockitoSpyBean.types();
+ Assert.state(types.length > 0,
+ "The @MockitoSpyBean 'types' attribute must not be empty when declared on a class");
+ Assert.state(mockitoSpyBean.name().isEmpty() || types.length == 1,
+ "The @MockitoSpyBean 'name' attribute cannot be used when mocking multiple types");
+ List handlers = new ArrayList<>();
+ for (Class> type : types) {
+ handlers.add(new MockitoSpyBeanOverrideHandler(ResolvableType.forClass(type), mockitoSpyBean));
+ }
+ return handlers;
}
- return handlers;
+ throw new IllegalStateException("""
+ Invalid annotation passed to MockitoBeanOverrideProcessor: \
+ expected either @MockitoBean or @MockitoSpyBean on test class %s"""
+ .formatted(testClass.getName()));
}
}
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 d10674b75331..e42c0b4563ba 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
@@ -18,6 +18,7 @@
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@@ -26,19 +27,40 @@
import org.springframework.test.context.bean.override.BeanOverride;
/**
- * {@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
+ * {@code @MockitoSpyBean} is an annotation that can be used in test classes 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
- * used to help disambiguate. In the absence of a {@code @Qualifier} annotation,
- * the name of the annotated field will be used as a fallback qualifier.
- * Alternatively, you can explicitly specify a bean name to spy by setting the
- * {@link #value() value} or {@link #name() name} attribute. If a bean name is
- * specified, it is required that a target bean with that name has been previously
- * registered in the application context.
+ *
{@code @MockitoSpyBean} 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 {@code @Nested} test class
+ * or in any class in the type hierarchy or enclosing class hierarchy above the
+ * {@code @Nested} 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 {@code @Nested} test class
+ * or on any class or interface in the type hierarchy or enclosing class hierarchy
+ * above the {@code @Nested} test class.
+ *
+ *
+ * When {@code @MockitoSpyBean} is declared on a field, the bean to spy is
+ * inferred from the type of the annotated field. If multiple candidates exist in
+ * the {@code ApplicationContext}, a {@code @Qualifier} annotation can be declared
+ * on the field to help disambiguate. In the absence of a {@code @Qualifier}
+ * annotation, the name of the annotated field will be used as a fallback
+ * qualifier. Alternatively, you can explicitly specify a bean name to spy
+ * by setting the {@link #value() value} or {@link #name() name} attribute. If a
+ * bean name is specified, it is required that a target bean with that name has
+ * been previously registered in the application context.
+ *
+ *
When {@code @MockitoSpyBean} is declared at the type level, the type of bean
+ * (or beans) to spy must be supplied via the {@link #types() types} attribute.
+ * If multiple candidates exist in the {@code ApplicationContext}, you can
+ * explicitly specify a bean name to spy by setting the {@link #name() name}
+ * attribute. Note, however, that the {@code types} attribute must contain a
+ * single type if an explicit bean {@code name} is configured.
*
*
A spy cannot be created for components which are known to the application
* context but are not beans — for example, components
@@ -56,24 +78,33 @@
* (default visibility), or {@code private} depending on the needs or coding
* practices of the project.
*
- *
{@code @MockitoSpyBean} fields will be inherited from an enclosing test class by default.
- * See {@link org.springframework.test.context.NestedTestConfiguration @NestedTestConfiguration}
+ *
{@code @MockitoSpyBean} fields and type-level {@code @MockitoSpyBean} declarations
+ * will be inherited from an enclosing test class by default. See
+ * {@link org.springframework.test.context.NestedTestConfiguration @NestedTestConfiguration}
* for details.
*
+ *
{@code @MockitoSpyBean} may be used as a meta-annotation to create
+ * custom composed annotations — for example, to define common spy
+ * configuration in a single annotation that can be reused across a test suite.
+ * {@code @MockitoSpyBean} can also be used as a {@linkplain Repeatable repeatable}
+ * annotation at the type level — for example, to spy on several beans by
+ * {@link #name() name}.
+ *
* @author Simon Baslé
* @author Sam Brannen
* @since 6.2
* @see org.springframework.test.context.bean.override.mockito.MockitoBean @MockitoBean
* @see org.springframework.test.context.bean.override.convention.TestBean @TestBean
*/
-@Target(ElementType.FIELD)
+@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
+@Repeatable(MockitoSpyBeans.class)
@BeanOverride(MockitoBeanOverrideProcessor.class)
public @interface MockitoSpyBean {
/**
- * Alias for {@link #name()}.
+ * Alias for {@link #name() name}.
*
Intended to be used when no other attributes are needed — for
* example, {@code @MockitoSpyBean("customBeanName")}.
* @see #name()
@@ -84,13 +115,27 @@
/**
* Name of the bean to spy.
*
If left unspecified, the bean to spy is selected according to the
- * annotated field's type, taking qualifiers into account if necessary. See
- * the {@linkplain MockitoSpyBean class-level documentation} for details.
+ * configured {@link #types() types} or the annotated field's type, taking
+ * qualifiers into account if necessary. See the {@linkplain MockitoSpyBean
+ * class-level documentation} for details.
* @see #value()
*/
@AliasFor("value")
String name() default "";
+ /**
+ * One or more types to spy.
+ *
Defaults to none.
+ *
Each type specified will result in a spy being created and registered
+ * with the {@code ApplicationContext}.
+ *
Types must be omitted when the annotation is used on a field.
+ *
When {@code @MockitoSpyBean} also defines a {@link #name name}, this
+ * attribute can only contain a single value.
+ * @return the types to spy
+ * @since 6.2.3
+ */
+ Class>[] types() default {};
+
/**
* The reset mode to apply to the spied bean.
*
The default is {@link MockReset#AFTER} meaning that spies are automatically
diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideHandler.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideHandler.java
index 1adba89bb8cb..ce3f11cbe204 100644
--- a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideHandler.java
+++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideHandler.java
@@ -48,7 +48,11 @@ class MockitoSpyBeanOverrideHandler extends AbstractMockitoBeanOverrideHandler {
new SpringAopBypassingVerificationStartedListener();
- MockitoSpyBeanOverrideHandler(Field field, ResolvableType typeToSpy, MockitoSpyBean spyBean) {
+ MockitoSpyBeanOverrideHandler(ResolvableType typeToSpy, MockitoSpyBean spyBean) {
+ this(null, typeToSpy, spyBean);
+ }
+
+ MockitoSpyBeanOverrideHandler(@Nullable Field field, ResolvableType typeToSpy, MockitoSpyBean spyBean) {
super(field, typeToSpy, (StringUtils.hasText(spyBean.name()) ? spyBean.name() : null),
BeanOverrideStrategy.WRAP, spyBean.reset());
Assert.notNull(typeToSpy, "typeToSpy must not be null");
diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeans.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeans.java
new file mode 100644
index 000000000000..2482b96f2477
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeans.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.bean.override.mockito;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Container for {@link MockitoSpyBean @MockitoSpyBean} annotations which allows
+ * {@code @MockitoSpyBean} to be used as a {@linkplain java.lang.annotation.Repeatable
+ * repeatable annotation} at the type level — for example, on test classes
+ * or interfaces implemented by test classes.
+ *
+ * @author Sam Brannen
+ * @since 6.2.3
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface MockitoSpyBeans {
+
+ MockitoSpyBean[] value();
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessorTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessorTests.java
index 7f4d07c25c44..5ac9041ac6c5 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessorTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessorTests.java
@@ -107,91 +107,197 @@ static class NameNotSupportedTestCase {
class CreateHandlersTests {
@Test
- void missingTypes() {
- Class> testClass = MissingTypesTestCase.class;
- MockitoBean annotation = testClass.getAnnotation(MockitoBean.class);
+ void otherAnnotationThrows() {
+ Annotation annotation = getClass().getAnnotation(Nested.class);
assertThatIllegalStateException()
- .isThrownBy(() -> processor.createHandlers(annotation, testClass))
- .withMessage("The @MockitoBean 'types' attribute must not be empty when declared on a class");
+ .isThrownBy(() -> processor.createHandlers(annotation, getClass()))
+ .withMessage("Invalid annotation passed to MockitoBeanOverrideProcessor: expected either " +
+ "@MockitoBean or @MockitoSpyBean on test class %s", getClass().getName());
}
- @Test
- void nameNotSupportedWithMultipleTypes() {
- Class> testClass = NameNotSupportedWithMultipleTypesTestCase.class;
- MockitoBean annotation = testClass.getAnnotation(MockitoBean.class);
+ @Nested
+ class MockitoBeanTests {
+
+ @Test
+ void missingTypes() {
+ Class> testClass = MissingTypesTestCase.class;
+ MockitoBean annotation = testClass.getAnnotation(MockitoBean.class);
+
+ assertThatIllegalStateException()
+ .isThrownBy(() -> processor.createHandlers(annotation, testClass))
+ .withMessage("The @MockitoBean 'types' attribute must not be empty when declared on a class");
+ }
+
+ @Test
+ void nameNotSupportedWithMultipleTypes() {
+ Class> testClass = NameNotSupportedWithMultipleTypesTestCase.class;
+ MockitoBean annotation = testClass.getAnnotation(MockitoBean.class);
+
+ assertThatIllegalStateException()
+ .isThrownBy(() -> processor.createHandlers(annotation, testClass))
+ .withMessage("The @MockitoBean 'name' attribute cannot be used when mocking multiple types");
+ }
+
+ @Test
+ void singleMockByType() {
+ Class> testClass = SingleMockByTypeTestCase.class;
+ MockitoBean annotation = testClass.getAnnotation(MockitoBean.class);
+ List handlers = processor.createHandlers(annotation, testClass);
+
+ assertThat(handlers).singleElement().isInstanceOfSatisfying(MockitoBeanOverrideHandler.class, handler -> {
+ assertThat(handler.getField()).isNull();
+ assertThat(handler.getBeanName()).isNull();
+ assertThat(handler.getBeanType().resolve()).isEqualTo(Integer.class);
+ });
+ }
+
+ @Test
+ void singleMockByName() {
+ Class> testClass = SingleMockByNameTestCase.class;
+ MockitoBean annotation = testClass.getAnnotation(MockitoBean.class);
+ List handlers = processor.createHandlers(annotation, testClass);
+
+ assertThat(handlers).singleElement().isInstanceOfSatisfying(MockitoBeanOverrideHandler.class, handler -> {
+ assertThat(handler.getField()).isNull();
+ assertThat(handler.getBeanName()).isEqualTo("enigma");
+ assertThat(handler.getBeanType().resolve()).isEqualTo(Integer.class);
+ });
+ }
+
+ @Test
+ void multipleMocks() {
+ Class> testClass = MultipleMocksTestCase.class;
+ MockitoBean annotation = testClass.getAnnotation(MockitoBean.class);
+ List handlers = processor.createHandlers(annotation, testClass);
+
+ assertThat(handlers).satisfiesExactly(
+ handler1 -> {
+ assertThat(handler1.getField()).isNull();
+ assertThat(handler1.getBeanName()).isNull();
+ assertThat(handler1.getBeanType().resolve()).isEqualTo(Integer.class);
+ },
+ handler2 -> {
+ assertThat(handler2.getField()).isNull();
+ assertThat(handler2.getBeanName()).isNull();
+ assertThat(handler2.getBeanType().resolve()).isEqualTo(Float.class);
+ }
+ );
+ }
- assertThatIllegalStateException()
- .isThrownBy(() -> processor.createHandlers(annotation, testClass))
- .withMessage("The @MockitoBean 'name' attribute cannot be used when mocking multiple types");
- }
- @Test
- void singleMockByType() {
- Class> testClass = SingleMockByTypeTestCase.class;
- MockitoBean annotation = testClass.getAnnotation(MockitoBean.class);
- List handlers = processor.createHandlers(annotation, testClass);
-
- assertThat(handlers).singleElement().isInstanceOfSatisfying(MockitoBeanOverrideHandler.class, handler -> {
- assertThat(handler.getField()).isNull();
- assertThat(handler.getBeanName()).isNull();
- assertThat(handler.getBeanType().resolve()).isEqualTo(Integer.class);
- });
- }
+ @MockitoBean
+ static class MissingTypesTestCase {
+ }
- @Test
- void singleMockByName() {
- Class> testClass = SingleMockByNameTestCase.class;
- MockitoBean annotation = testClass.getAnnotation(MockitoBean.class);
- List handlers = processor.createHandlers(annotation, testClass);
-
- assertThat(handlers).singleElement().isInstanceOfSatisfying(MockitoBeanOverrideHandler.class, handler -> {
- assertThat(handler.getField()).isNull();
- assertThat(handler.getBeanName()).isEqualTo("enigma");
- assertThat(handler.getBeanType().resolve()).isEqualTo(Integer.class);
- });
- }
+ @MockitoBean(name = "bogus", types = { Integer.class, Float.class })
+ static class NameNotSupportedWithMultipleTypesTestCase {
+ }
- @Test
- void multipleMocks() {
- Class> testClass = MultipleMocksTestCase.class;
- MockitoBean annotation = testClass.getAnnotation(MockitoBean.class);
- List handlers = processor.createHandlers(annotation, testClass);
-
- assertThat(handlers).satisfiesExactly(
- handler1 -> {
- assertThat(handler1.getField()).isNull();
- assertThat(handler1.getBeanName()).isNull();
- assertThat(handler1.getBeanType().resolve()).isEqualTo(Integer.class);
- },
- handler2 -> {
- assertThat(handler2.getField()).isNull();
- assertThat(handler2.getBeanName()).isNull();
- assertThat(handler2.getBeanType().resolve()).isEqualTo(Float.class);
- }
- );
- }
+ @MockitoBean(types = Integer.class)
+ static class SingleMockByTypeTestCase {
+ }
+ @MockitoBean(name = "enigma", types = Integer.class)
+ static class SingleMockByNameTestCase {
+ }
- @MockitoBean
- static class MissingTypesTestCase {
+ @MockitoBean(types = { Integer.class, Float.class })
+ static class MultipleMocksTestCase {
+ }
}
- @MockitoBean(name = "bogus", types = { Integer.class, Float.class })
- static class NameNotSupportedWithMultipleTypesTestCase {
- }
+ @Nested
+ class MockitoSpyBeanTests {
+
+ @Test
+ void missingTypes() {
+ Class> testClass = MissingTypesTestCase.class;
+ MockitoSpyBean annotation = testClass.getAnnotation(MockitoSpyBean.class);
+
+ assertThatIllegalStateException()
+ .isThrownBy(() -> processor.createHandlers(annotation, testClass))
+ .withMessage("The @MockitoSpyBean 'types' attribute must not be empty when declared on a class");
+ }
+
+ @Test
+ void nameNotSupportedWithMultipleTypes() {
+ Class> testClass = NameNotSupportedWithMultipleTypesTestCase.class;
+ MockitoSpyBean annotation = testClass.getAnnotation(MockitoSpyBean.class);
+
+ assertThatIllegalStateException()
+ .isThrownBy(() -> processor.createHandlers(annotation, testClass))
+ .withMessage("The @MockitoSpyBean 'name' attribute cannot be used when mocking multiple types");
+ }
+
+ @Test
+ void singleSpyByType() {
+ Class> testClass = SingleSpyByTypeTestCase.class;
+ MockitoSpyBean annotation = testClass.getAnnotation(MockitoSpyBean.class);
+ List handlers = processor.createHandlers(annotation, testClass);
+
+ assertThat(handlers).singleElement().isInstanceOfSatisfying(MockitoSpyBeanOverrideHandler.class, handler -> {
+ assertThat(handler.getField()).isNull();
+ assertThat(handler.getBeanName()).isNull();
+ assertThat(handler.getBeanType().resolve()).isEqualTo(Integer.class);
+ });
+ }
+
+ @Test
+ void singleSpyByName() {
+ Class> testClass = SingleSpyByNameTestCase.class;
+ MockitoSpyBean annotation = testClass.getAnnotation(MockitoSpyBean.class);
+ List handlers = processor.createHandlers(annotation, testClass);
+
+ assertThat(handlers).singleElement().isInstanceOfSatisfying(MockitoSpyBeanOverrideHandler.class, handler -> {
+ assertThat(handler.getField()).isNull();
+ assertThat(handler.getBeanName()).isEqualTo("enigma");
+ assertThat(handler.getBeanType().resolve()).isEqualTo(Integer.class);
+ });
+ }
+
+ @Test
+ void multipleSpies() {
+ Class> testClass = MultipleSpiesTestCase.class;
+ MockitoSpyBean annotation = testClass.getAnnotation(MockitoSpyBean.class);
+ List handlers = processor.createHandlers(annotation, testClass);
+
+ assertThat(handlers).satisfiesExactly(
+ handler1 -> {
+ assertThat(handler1.getField()).isNull();
+ assertThat(handler1.getBeanName()).isNull();
+ assertThat(handler1.getBeanType().resolve()).isEqualTo(Integer.class);
+ },
+ handler2 -> {
+ assertThat(handler2.getField()).isNull();
+ assertThat(handler2.getBeanName()).isNull();
+ assertThat(handler2.getBeanType().resolve()).isEqualTo(Float.class);
+ }
+ );
+ }
- @MockitoBean(types = Integer.class)
- static class SingleMockByTypeTestCase {
- }
- @MockitoBean(name = "enigma", types = Integer.class)
- static class SingleMockByNameTestCase {
- }
+ @MockitoSpyBean
+ static class MissingTypesTestCase {
+ }
+
+ @MockitoSpyBean(name = "bogus", types = { Integer.class, Float.class })
+ static class NameNotSupportedWithMultipleTypesTestCase {
+ }
+
+ @MockitoSpyBean(types = Integer.class)
+ static class SingleSpyByTypeTestCase {
+ }
- @MockitoBean(types = { Integer.class, Float.class })
- static class MultipleMocksTestCase {
+ @MockitoSpyBean(name = "enigma", types = Integer.class)
+ static class SingleSpyByNameTestCase {
+ }
+
+ @MockitoSpyBean(types = { Integer.class, Float.class })
+ static class MultipleSpiesTestCase {
+ }
}
+
}
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/TestInterface01.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockTestInterface01.java
similarity index 87%
rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/TestInterface01.java
rename to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockTestInterface01.java
index a28e7d495984..394a04b9b116 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/TestInterface01.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockTestInterface01.java
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package org.springframework.test.context.bean.override.mockito.mockbeans;
+package org.springframework.test.context.bean.override.mockito.typelevel;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
@MockitoBean(types = Service01.class)
-interface TestInterface01 {
+interface MockTestInterface01 {
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/TestInterface08.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockTestInterface08.java
similarity index 87%
rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/TestInterface08.java
rename to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockTestInterface08.java
index 45d326882210..4aeeced98230 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/TestInterface08.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockTestInterface08.java
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package org.springframework.test.context.bean.override.mockito.mockbeans;
+package org.springframework.test.context.bean.override.mockito.typelevel;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
@MockitoBean(types = Service08.class)
-interface TestInterface08 {
+interface MockTestInterface08 {
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/TestInterface11.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockTestInterface11.java
similarity index 87%
rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/TestInterface11.java
rename to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockTestInterface11.java
index 2bfe47e028b4..1f8e1511b521 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/TestInterface11.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockTestInterface11.java
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package org.springframework.test.context.bean.override.mockito.mockbeans;
+package org.springframework.test.context.bean.override.mockito.typelevel;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
@MockitoBean(types = Service11.class)
-interface TestInterface11 {
+interface MockTestInterface11 {
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/MockitoBeansByNameIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockitoBeansByNameIntegrationTests.java
similarity index 88%
rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/MockitoBeansByNameIntegrationTests.java
rename to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockitoBeansByNameIntegrationTests.java
index b27e2dd860af..272f767e4a98 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/MockitoBeansByNameIntegrationTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockitoBeansByNameIntegrationTests.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.test.context.bean.override.mockito.mockbeans;
+package org.springframework.test.context.bean.override.mockito.typelevel;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -30,6 +30,8 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
+import static org.springframework.test.mockito.MockitoAssertions.assertIsMock;
+import static org.springframework.test.mockito.MockitoAssertions.assertIsNotMock;
/**
* Integration tests for {@link MockitoBeans @MockitoBeans} and
@@ -69,12 +71,18 @@ void configureMocks() {
@Test
void checkMocksAndStandardBean() {
+ assertIsMock(s1, "s1");
+ assertIsMock(s2, "s2");
+ assertIsMock(service3, "service3");
+ assertIsNotMock(service4, "service4");
+
assertThat(s1.greeting()).isEqualTo("mock 1");
assertThat(s2.greeting()).isEqualTo("mock 2");
assertThat(service3.greeting()).isEqualTo("mock 3");
assertThat(service4.greeting()).isEqualTo("prod 4");
}
+
@Configuration
static class Config {
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/MockitoBeansByTypeIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockitoBeansByTypeIntegrationTests.java
similarity index 78%
rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/MockitoBeansByTypeIntegrationTests.java
rename to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockitoBeansByTypeIntegrationTests.java
index e58b2e888ce1..ccae3865b345 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/MockitoBeansByTypeIntegrationTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockitoBeansByTypeIntegrationTests.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.test.context.bean.override.mockito.mockbeans;
+package org.springframework.test.context.bean.override.mockito.typelevel;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
@@ -27,6 +27,7 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
+import static org.springframework.test.mockito.MockitoAssertions.assertIsMock;
/**
* Integration tests for {@link MockitoBeans @MockitoBeans} and
@@ -42,7 +43,7 @@
@MockitoBean(types = {Service04.class, Service05.class})
@SharedMocks // Intentionally declared between local @MockitoBean declarations
@MockitoBean(types = Service06.class)
-class MockitoBeansByTypeIntegrationTests implements TestInterface01 {
+class MockitoBeansByTypeIntegrationTests implements MockTestInterface01 {
@Autowired
Service01 service01;
@@ -79,6 +80,14 @@ void configureMocks() {
@Test
void checkMocks() {
+ assertIsMock(service01, "service01");
+ assertIsMock(service02, "service02");
+ assertIsMock(service03, "service03");
+ assertIsMock(service04, "service04");
+ assertIsMock(service05, "service05");
+ assertIsMock(service06, "service06");
+ assertIsMock(service07, "service07");
+
assertThat(service01.greeting()).isEqualTo("mock 01");
assertThat(service02.greeting()).isEqualTo("mock 02");
assertThat(service03.greeting()).isEqualTo("mock 03");
@@ -90,7 +99,7 @@ void checkMocks() {
@MockitoBean(types = Service09.class)
- class BaseTestCase implements TestInterface08 {
+ class BaseTestCase implements MockTestInterface08 {
@Autowired
Service08 service08;
@@ -104,7 +113,7 @@ class BaseTestCase implements TestInterface08 {
@Nested
@MockitoBean(types = Service12.class)
- class NestedTests extends BaseTestCase implements TestInterface11 {
+ class NestedTests extends BaseTestCase implements MockTestInterface11 {
@Autowired
Service11 service11;
@@ -128,6 +137,20 @@ void configureMocks() {
@Test
void checkMocks() {
+ assertIsMock(service01, "service01");
+ assertIsMock(service02, "service02");
+ assertIsMock(service03, "service03");
+ assertIsMock(service04, "service04");
+ assertIsMock(service05, "service05");
+ assertIsMock(service06, "service06");
+ assertIsMock(service07, "service07");
+ assertIsMock(service08, "service08");
+ assertIsMock(service09, "service09");
+ assertIsMock(service10, "service10");
+ assertIsMock(service11, "service11");
+ assertIsMock(service12, "service12");
+ assertIsMock(service13, "service13");
+
assertThat(service01.greeting()).isEqualTo("mock 01");
assertThat(service02.greeting()).isEqualTo("mock 02");
assertThat(service03.greeting()).isEqualTo("mock 03");
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/MockitoBeansTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockitoBeansTests.java
similarity index 97%
rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/MockitoBeansTests.java
rename to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockitoBeansTests.java
index 3ef3e14ea2e2..28b4f87e8c72 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/MockitoBeansTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockitoBeansTests.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.test.context.bean.override.mockito.mockbeans;
+package org.springframework.test.context.bean.override.mockito.typelevel;
import java.util.stream.Stream;
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockitoSpyBeansByNameIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockitoSpyBeansByNameIntegrationTests.java
new file mode 100644
index 000000000000..b35c03aebd71
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockitoSpyBeansByNameIntegrationTests.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2002-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.bean.override.mockito.typelevel;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.bean.override.example.ExampleService;
+import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
+import org.springframework.test.context.bean.override.mockito.MockitoSpyBeans;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.BDDMockito.given;
+import static org.springframework.test.mockito.MockitoAssertions.assertIsNotMock;
+import static org.springframework.test.mockito.MockitoAssertions.assertIsNotSpy;
+import static org.springframework.test.mockito.MockitoAssertions.assertIsSpy;
+
+/**
+ * Integration tests for {@link MockitoSpyBeans @MockitoSpyBeans} and
+ * {@link MockitoSpyBean @MockitoSpyBean} declared "by name" at the class level
+ * as a repeatable annotation.
+ *
+ * @author Sam Brannen
+ * @since 6.2.3
+ * @see gh-34408
+ * @see MockitoSpyBeansByTypeIntegrationTests
+ */
+@SpringJUnitConfig
+@MockitoSpyBean(name = "s1", types = ExampleService.class)
+@MockitoSpyBean(name = "s2", types = ExampleService.class)
+class MockitoSpyBeansByNameIntegrationTests {
+
+ @Autowired
+ ExampleService s1;
+
+ @Autowired
+ ExampleService s2;
+
+ @MockitoSpyBean(name = "s3")
+ ExampleService service3;
+
+ @Autowired
+ @Qualifier("s4")
+ ExampleService service4;
+
+
+ @BeforeEach
+ void configureSpies() {
+ given(s1.greeting()).willReturn("spy 1");
+ given(s2.greeting()).willReturn("spy 2");
+ given(service3.greeting()).willReturn("spy 3");
+ }
+
+ @Test
+ void checkSpiesAndStandardBean() {
+ assertIsSpy(s1, "s1");
+ assertIsSpy(s2, "s2");
+ assertIsSpy(service3, "service3");
+ assertIsNotMock(service4, "service4");
+ assertIsNotSpy(service4, "service4");
+
+ assertThat(s1.greeting()).isEqualTo("spy 1");
+ assertThat(s2.greeting()).isEqualTo("spy 2");
+ assertThat(service3.greeting()).isEqualTo("spy 3");
+ assertThat(service4.greeting()).isEqualTo("prod 4");
+ }
+
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ ExampleService s1() {
+ return new ExampleService() {
+ @Override
+ public String greeting() {
+ return "prod 1";
+ }
+ };
+ }
+
+ @Bean
+ ExampleService s2() {
+ return new ExampleService() {
+ @Override
+ public String greeting() {
+ return "prod 2";
+ }
+ };
+ }
+
+ @Bean
+ ExampleService s3() {
+ return new ExampleService() {
+ @Override
+ public String greeting() {
+ return "prod 3";
+ }
+ };
+ }
+
+ @Bean
+ ExampleService s4() {
+ return () -> "prod 4";
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockitoSpyBeansByTypeIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockitoSpyBeansByTypeIntegrationTests.java
new file mode 100644
index 000000000000..5454976fa2e2
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockitoSpyBeansByTypeIntegrationTests.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2002-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.bean.override.mockito.typelevel;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
+import org.springframework.test.context.bean.override.mockito.MockitoSpyBeans;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.BDDMockito.given;
+import static org.springframework.test.mockito.MockitoAssertions.assertIsSpy;
+
+/**
+ * Integration tests for {@link MockitoSpyBeans @MockitoSpyBeans} and
+ * {@link MockitoSpyBean @MockitoSpyBean} declared "by type" at the class level,
+ * as a repeatable annotation, and via a custom composed annotation.
+ *
+ * @author Sam Brannen
+ * @since 6.2.3
+ * @see gh-34408
+ * @see MockitoSpyBeansByNameIntegrationTests
+ */
+@SpringJUnitConfig
+@MockitoSpyBean(types = {Service04.class, Service05.class})
+@SharedSpies // Intentionally declared between local @MockitoSpyBean declarations
+@MockitoSpyBean(types = Service06.class)
+class MockitoSpyBeansByTypeIntegrationTests implements SpyTestInterface01 {
+
+ @Autowired
+ Service01 service01;
+
+ @Autowired
+ Service02 service02;
+
+ @Autowired
+ Service03 service03;
+
+ @Autowired
+ Service04 service04;
+
+ @Autowired
+ Service05 service05;
+
+ @Autowired
+ Service06 service06;
+
+ @MockitoSpyBean
+ Service07 service07;
+
+
+ @BeforeEach
+ void configureSpies() {
+ given(service01.greeting()).willReturn("spy 01");
+ given(service02.greeting()).willReturn("spy 02");
+ given(service03.greeting()).willReturn("spy 03");
+ given(service04.greeting()).willReturn("spy 04");
+ given(service05.greeting()).willReturn("spy 05");
+ given(service06.greeting()).willReturn("spy 06");
+ given(service07.greeting()).willReturn("spy 07");
+ }
+
+ @Test
+ void checkSpies() {
+ assertIsSpy(service01, "service01");
+ assertIsSpy(service02, "service02");
+ assertIsSpy(service03, "service03");
+ assertIsSpy(service04, "service04");
+ assertIsSpy(service05, "service05");
+ assertIsSpy(service06, "service06");
+ assertIsSpy(service07, "service07");
+
+ assertThat(service01.greeting()).isEqualTo("spy 01");
+ assertThat(service02.greeting()).isEqualTo("spy 02");
+ assertThat(service03.greeting()).isEqualTo("spy 03");
+ assertThat(service04.greeting()).isEqualTo("spy 04");
+ assertThat(service05.greeting()).isEqualTo("spy 05");
+ assertThat(service06.greeting()).isEqualTo("spy 06");
+ assertThat(service07.greeting()).isEqualTo("spy 07");
+ }
+
+
+ @MockitoSpyBean(types = Service09.class)
+ class BaseTestCase implements SpyTestInterface08 {
+
+ @Autowired
+ Service08 service08;
+
+ @Autowired
+ Service09 service09;
+
+ @MockitoSpyBean
+ Service10 service10;
+ }
+
+ @Nested
+ @MockitoSpyBean(types = Service12.class)
+ class NestedTests extends BaseTestCase implements SpyTestInterface11 {
+
+ @Autowired
+ Service11 service11;
+
+ @Autowired
+ Service12 service12;
+
+ @MockitoSpyBean
+ Service13 service13;
+
+
+ @BeforeEach
+ void configureSpies() {
+ given(service08.greeting()).willReturn("spy 08");
+ given(service09.greeting()).willReturn("spy 09");
+ given(service10.greeting()).willReturn("spy 10");
+ given(service11.greeting()).willReturn("spy 11");
+ given(service12.greeting()).willReturn("spy 12");
+ given(service13.greeting()).willReturn("spy 13");
+ }
+
+ @Test
+ void checkSpies() {
+ assertIsSpy(service01, "service01");
+ assertIsSpy(service02, "service02");
+ assertIsSpy(service03, "service03");
+ assertIsSpy(service04, "service04");
+ assertIsSpy(service05, "service05");
+ assertIsSpy(service06, "service06");
+ assertIsSpy(service07, "service07");
+ assertIsSpy(service08, "service08");
+ assertIsSpy(service09, "service09");
+ assertIsSpy(service10, "service10");
+ assertIsSpy(service11, "service11");
+ assertIsSpy(service12, "service12");
+ assertIsSpy(service13, "service13");
+
+ assertThat(service01.greeting()).isEqualTo("spy 01");
+ assertThat(service02.greeting()).isEqualTo("spy 02");
+ assertThat(service03.greeting()).isEqualTo("spy 03");
+ assertThat(service04.greeting()).isEqualTo("spy 04");
+ assertThat(service05.greeting()).isEqualTo("spy 05");
+ assertThat(service06.greeting()).isEqualTo("spy 06");
+ assertThat(service07.greeting()).isEqualTo("spy 07");
+ assertThat(service08.greeting()).isEqualTo("spy 08");
+ assertThat(service09.greeting()).isEqualTo("spy 09");
+ assertThat(service10.greeting()).isEqualTo("spy 10");
+ assertThat(service11.greeting()).isEqualTo("spy 11");
+ assertThat(service12.greeting()).isEqualTo("spy 12");
+ assertThat(service13.greeting()).isEqualTo("spy 13");
+ }
+ }
+
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ Service01 service01() {
+ return new Service01() {
+ @Override
+ public String greeting() {
+ return "prod 1";
+ }
+ };
+ }
+
+ @Bean
+ Service02 service02() {
+ return new Service02() {
+ @Override
+ public String greeting() {
+ return "prod 2";
+ }
+ };
+ }
+
+ @Bean
+ Service03 service03() {
+ return new Service03() {
+ @Override
+ public String greeting() {
+ return "prod 3";
+ }
+ };
+ }
+
+ @Bean
+ Service04 service04() {
+ return new Service04() {
+ @Override
+ public String greeting() {
+ return "prod 4";
+ }
+ };
+ }
+
+ @Bean
+ Service05 service05() {
+ return new Service05() {
+ @Override
+ public String greeting() {
+ return "prod 5";
+ }
+ };
+ }
+
+ @Bean
+ Service06 service06() {
+ return new Service06() {
+ @Override
+ public String greeting() {
+ return "prod 6";
+ }
+ };
+ }
+
+ @Bean
+ Service07 service07() {
+ return new Service07() {
+ @Override
+ public String greeting() {
+ return "prod 7";
+ }
+ };
+ }
+
+ @Bean
+ Service08 service08() {
+ return new Service08() {
+ @Override
+ public String greeting() {
+ return "prod 8";
+ }
+ };
+ }
+
+ @Bean
+ Service09 service09() {
+ return new Service09() {
+ @Override
+ public String greeting() {
+ return "prod 9";
+ }
+ };
+ }
+
+ @Bean
+ Service10 service10() {
+ return new Service10() {
+ @Override
+ public String greeting() {
+ return "prod 10";
+ }
+ };
+ }
+
+ @Bean
+ Service11 service11() {
+ return new Service11() {
+ @Override
+ public String greeting() {
+ return "prod 11";
+ }
+ };
+ }
+
+ @Bean
+ Service12 service12() {
+ return new Service12() {
+ @Override
+ public String greeting() {
+ return "prod 12";
+ }
+ };
+ }
+
+ @Bean
+ Service13 service13() {
+ return new Service13() {
+ @Override
+ public String greeting() {
+ return "prod 13";
+ }
+ };
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockitoSpyBeansTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockitoSpyBeansTests.java
new file mode 100644
index 000000000000..af0985e456e7
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/MockitoSpyBeansTests.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2002-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.bean.override.mockito.typelevel;
+
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.core.ResolvableType;
+import org.springframework.test.context.bean.override.BeanOverrideHandler;
+import org.springframework.test.context.bean.override.BeanOverrideTestUtils;
+import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
+import org.springframework.test.context.bean.override.mockito.MockitoSpyBeans;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link MockitoSpyBeans @MockitoSpyBeans}: {@link MockitoSpyBean @MockitoSpyBean}
+ * declared at the class level, as a repeatable annotation, and via a custom composed
+ * annotation.
+ *
+ * @author Sam Brannen
+ * @since 6.2.3
+ * @see gh-34408
+ */
+class MockitoSpyBeansTests {
+
+ @Test
+ void registrationOrderForTopLevelClass() {
+ Stream> mockedServices = getRegisteredMockTypes(MockitoSpyBeansByTypeIntegrationTests.class);
+ assertThat(mockedServices).containsExactly(
+ Service01.class, Service02.class, Service03.class, Service04.class,
+ Service05.class, Service06.class, Service07.class);
+ }
+
+ @Test
+ void registrationOrderForNestedClass() {
+ Stream> mockedServices = getRegisteredMockTypes(MockitoSpyBeansByTypeIntegrationTests.NestedTests.class);
+ assertThat(mockedServices).containsExactly(
+ Service01.class, Service02.class, Service03.class, Service04.class,
+ Service05.class, Service06.class, Service07.class, Service08.class,
+ Service09.class, Service10.class, Service11.class, Service12.class,
+ Service13.class);
+ }
+
+
+ private static Stream> getRegisteredMockTypes(Class> testClass) {
+ return BeanOverrideTestUtils.findAllHandlers(testClass)
+ .stream()
+ .map(BeanOverrideHandler::getBeanType)
+ .map(ResolvableType::getRawClass);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service.java
similarity index 90%
rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service.java
rename to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service.java
index 187ffeb6a833..e0092704f82b 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.test.context.bean.override.mockito.mockbeans;
+package org.springframework.test.context.bean.override.mockito.typelevel;
interface Service {
String greeting();
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service01.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service01.java
similarity index 89%
rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service01.java
rename to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service01.java
index a2110bf7fb5a..e99bb762659a 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service01.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service01.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.test.context.bean.override.mockito.mockbeans;
+package org.springframework.test.context.bean.override.mockito.typelevel;
interface Service01 extends Service {
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service02.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service02.java
similarity index 89%
rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service02.java
rename to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service02.java
index c2b62a558eec..78de4a2efb0e 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service02.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service02.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.test.context.bean.override.mockito.mockbeans;
+package org.springframework.test.context.bean.override.mockito.typelevel;
interface Service02 extends Service {
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service03.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service03.java
similarity index 89%
rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service03.java
rename to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service03.java
index 31fe690f5944..564a48ceac1d 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service03.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service03.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.test.context.bean.override.mockito.mockbeans;
+package org.springframework.test.context.bean.override.mockito.typelevel;
interface Service03 extends Service {
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service04.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service04.java
similarity index 89%
rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service04.java
rename to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service04.java
index d32ba233d478..25c70404d1fc 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service04.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service04.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.test.context.bean.override.mockito.mockbeans;
+package org.springframework.test.context.bean.override.mockito.typelevel;
interface Service04 extends Service {
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service05.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service05.java
similarity index 89%
rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service05.java
rename to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service05.java
index 8c738aa36dde..d8da96f5466b 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service05.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service05.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.test.context.bean.override.mockito.mockbeans;
+package org.springframework.test.context.bean.override.mockito.typelevel;
interface Service05 extends Service {
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service06.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service06.java
similarity index 89%
rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service06.java
rename to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service06.java
index bceab3996501..00adf7c28e5a 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service06.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service06.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.test.context.bean.override.mockito.mockbeans;
+package org.springframework.test.context.bean.override.mockito.typelevel;
interface Service06 extends Service {
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service07.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service07.java
similarity index 89%
rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service07.java
rename to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service07.java
index 35e82c9fdad8..d3715f93748d 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service07.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service07.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.test.context.bean.override.mockito.mockbeans;
+package org.springframework.test.context.bean.override.mockito.typelevel;
interface Service07 extends Service {
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service08.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service08.java
similarity index 89%
rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service08.java
rename to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service08.java
index d9630716cf73..af5f07ced931 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service08.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service08.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.test.context.bean.override.mockito.mockbeans;
+package org.springframework.test.context.bean.override.mockito.typelevel;
interface Service08 extends Service {
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service09.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service09.java
similarity index 89%
rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service09.java
rename to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service09.java
index cb5e92242eee..363f591882e7 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service09.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service09.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.test.context.bean.override.mockito.mockbeans;
+package org.springframework.test.context.bean.override.mockito.typelevel;
interface Service09 extends Service {
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service10.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service10.java
similarity index 89%
rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service10.java
rename to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service10.java
index 50231a138491..1fcaaff6eb7c 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service10.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service10.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.test.context.bean.override.mockito.mockbeans;
+package org.springframework.test.context.bean.override.mockito.typelevel;
interface Service10 extends Service {
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service11.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service11.java
similarity index 89%
rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service11.java
rename to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service11.java
index 3b9203d5ee88..1c8b7cb33e9c 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service11.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service11.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.test.context.bean.override.mockito.mockbeans;
+package org.springframework.test.context.bean.override.mockito.typelevel;
interface Service11 extends Service {
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service12.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service12.java
similarity index 89%
rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service12.java
rename to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service12.java
index 097d5ade6c47..386b41294c93 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service12.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service12.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.test.context.bean.override.mockito.mockbeans;
+package org.springframework.test.context.bean.override.mockito.typelevel;
interface Service12 extends Service {
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service13.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service13.java
similarity index 89%
rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service13.java
rename to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service13.java
index 233e52be6f25..5ac3a2828e47 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/Service13.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/Service13.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.test.context.bean.override.mockito.mockbeans;
+package org.springframework.test.context.bean.override.mockito.typelevel;
interface Service13 extends Service {
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/SharedMocks.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/SharedMocks.java
similarity index 93%
rename from spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/SharedMocks.java
rename to spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/SharedMocks.java
index e44017dc6af1..39c9056613df 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/mockbeans/SharedMocks.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/SharedMocks.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.test.context.bean.override.mockito.mockbeans;
+package org.springframework.test.context.bean.override.mockito.typelevel;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/SharedSpies.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/SharedSpies.java
new file mode 100644
index 000000000000..f3cb10d7ba87
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/SharedSpies.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2002-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.bean.override.mockito.typelevel;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@MockitoSpyBean(types = Service02.class)
+@MockitoSpyBean(types = Service03.class)
+@interface SharedSpies {
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/SpyTestInterface01.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/SpyTestInterface01.java
new file mode 100644
index 000000000000..9f8c24193964
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/SpyTestInterface01.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2002-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.bean.override.mockito.typelevel;
+
+import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
+
+@MockitoSpyBean(types = Service01.class)
+interface SpyTestInterface01 {
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/SpyTestInterface08.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/SpyTestInterface08.java
new file mode 100644
index 000000000000..37e74e64f05b
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/SpyTestInterface08.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2002-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.bean.override.mockito.typelevel;
+
+import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
+
+@MockitoSpyBean(types = Service08.class)
+interface SpyTestInterface08 {
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/SpyTestInterface11.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/SpyTestInterface11.java
new file mode 100644
index 000000000000..ed5bf93472b1
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/typelevel/SpyTestInterface11.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2002-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.bean.override.mockito.typelevel;
+
+import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
+
+@MockitoSpyBean(types = Service11.class)
+interface SpyTestInterface11 {
+}
diff --git a/spring-test/src/test/java/org/springframework/test/mockito/MockitoAssertions.java b/spring-test/src/test/java/org/springframework/test/mockito/MockitoAssertions.java
index b1d36e5dd306..c4bbfb814842 100644
--- a/spring-test/src/test/java/org/springframework/test/mockito/MockitoAssertions.java
+++ b/spring-test/src/test/java/org/springframework/test/mockito/MockitoAssertions.java
@@ -31,10 +31,12 @@ public abstract class MockitoAssertions {
public static void assertIsMock(Object obj) {
assertThat(isMock(obj)).as("is a Mockito mock").isTrue();
+ assertIsNotSpy(obj);
}
public static void assertIsMock(Object obj, String message) {
assertThat(isMock(obj)).as("%s is a Mockito mock", message).isTrue();
+ assertIsNotSpy(obj, message);
}
public static void assertIsNotMock(Object obj) {