Support @⁠MockitoBean at the type level on test classes

Prior to this commit, @⁠MockitoBean could only be declared on fields
within test classes, which prevented developers from being able to
easily reuse mock configuration across a test suite.

With this commit, @⁠MockitoBean is now supported at the type level on
test classes, their superclasses, and interfaces implemented by those
classes. @⁠MockitoBean is also supported on enclosing classes for
@⁠Nested test classes, their superclasses, and interfaces implemented
by those classes, while honoring @⁠NestedTestConfiguration semantics.

In addition, @⁠MockitoBean:

- has a new `types` attribute that can be used to declare the type or
  types to mock when @⁠MockitoBean is declared at the type level

- can be declared as a repeatable annotation at the type level

- can be declared as a meta-annotation on a custom composed annotation
  which can be reused across a test suite (see the @⁠SharedMocks
  example in the reference manual)

To support these new features, this commit also includes the following
changes.

- The `field` property in BeanOverrideHandler is now @⁠Nullable.

- BeanOverrideProcessor has a new `default` createHandlers() method
  which is invoked when a @⁠BeanOverride annotation is found at the
  type level.

- MockitoBeanOverrideProcessor implements the new createHandlers()
  method.

- The internal findHandlers() method in BeanOverrideHandler has been
  completely overhauled.

- The @⁠MockitoBean and @⁠MockitoSpyBean section of the reference
  manual has been completely overhauled.

Closes gh-33925
This commit is contained in:
Sam Brannen 2025-01-03 12:09:04 +02:00
parent 8b6523a35b
commit 9181cce65f
36 changed files with 1380 additions and 145 deletions

View File

@ -11,29 +11,21 @@ If multiple candidates match, `@Qualifier` can be provided to narrow the candida
override. Alternatively, a candidate whose bean name matches the name of the field will
match.
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` attribute
of the annotation.
[WARNING]
====
Qualifiers, including the name of the 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 tests, make sure to name the field consistently to avoid
the same bean in several test classes, make sure to name the field consistently to avoid
creating unnecessary contexts.
====
Each annotation also defines Mockito-specific attributes to fine-tune the mocking details.
Each annotation also defines Mockito-specific attributes to fine-tune the mocking behavior.
By default, the `@MockitoBean` annotation uses the `REPLACE_OR_CREATE`
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. As mentioned previously,
you can switch to the `REPLACE` strategy by setting the `enforceOverride` attribute to
`true`.
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.
The `@MockitoSpyBean` annotation uses the `WRAP`
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy],
@ -61,6 +53,17 @@ Such fields can therefore be `public`, `protected`, package-private (default vis
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:
[tabs]
@ -69,11 +72,13 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
class OverrideBeanTests {
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
@MockitoBean // <1>
CustomService customService;
// test case body...
// tests...
}
----
<1> Replace the bean with type `CustomService` with a Mockito `mock`.
@ -82,8 +87,8 @@ Java::
In the example above, we are creating a mock for `CustomService`. If more than one bean
of that type exists, the bean named `customService` is considered. Otherwise, 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 override. If no such bean exists, a bean definition
will be created with an auto-generated bean name.
`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:
@ -93,20 +98,43 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
class OverrideBeanTests {
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
@MockitoBean("service") // <1>
CustomService customService;
// test case body...
// tests...
}
----
<1> Replace the bean named `service` with a Mockito `mock`.
======
If no bean definition named `service` exists, one is created.
If no bean named `service` exists, one is created.
The following example shows how to use the default behavior of the `@MockitoSpyBean` annotation:
`@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]
======
@ -114,11 +142,70 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
class OverrideBeanTests {
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@MockitoBean(types = {OrderService.class, UserService.class}) // <1>
@MockitoBean(name = "ps1", types = PrintingService.class) // <2>
public @interface SharedMocks {
}
----
<1> Register `OrderService` and `UserService` mocks by-type.
<2> Register `PrintingService` mock by-name.
======
The following demonstrates how `@SharedMocks` can be used on a test class.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
@SharedMocks // <1>
class BeanOverrideTests {
@Autowired OrderService orderService; // <2>
@Autowired UserService userService; // <2>
@Autowired PrintingService ps1; // <2>
// Inject other components that rely on the mocks.
@Test
void testThatDependsOnMocks() {
// ...
}
}
----
<1> Register common mocks via the custom `@SharedMocks` annotation.
<2> Optionally inject mocks to _stub_ or _verify_ them.
======
TIP: The mocks 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.
[[spring-testing-annotation-beanoverriding-mockitospybean-examples]]
== `@MockitoSpyBean` Examples
The following example shows how to use the default behavior of the `@MockitoSpyBean`
annotation:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
@MockitoSpyBean // <1>
CustomService customService;
// test case body...
// tests...
}
----
<1> Wrap the bean with type `CustomService` with a Mockito `spy`.
@ -137,12 +224,13 @@ Java::
+
[source,java,indent=0,subs="verbatim,quotes"]
----
class OverrideBeanTests {
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
@MockitoSpyBean("service") // <1>
CustomService customService;
// test case body...
// tests...
}
----
<1> Wrap the bean named `service` with a Mockito `spy`.

View File

@ -25,15 +25,19 @@ import java.lang.annotation.Target;
import org.springframework.aot.hint.annotation.Reflective;
/**
* Mark a composed annotation as eligible for Bean Override processing.
* Mark a <em>composed annotation</em> as eligible for Bean Override processing.
*
* <p>Specifying this annotation registers the configured {@link BeanOverrideProcessor}
* which must be capable of handling the composed annotation and its attributes.
*
* <p>Since the composed annotation should only be applied to non-static fields, it is
* expected that it is meta-annotated with {@link Target @Target(ElementType.FIELD)}.
* <p>Since the composed annotation will typically only be applied to non-static
* fields, it is expected that the composed annotation is meta-annotated with
* {@link Target @Target(ElementType.FIELD)}. However, certain bean override
* annotations may be declared with an additional {@code ElementType.TYPE} target
* for use at the type level, as is the case for {@code @MockitoBean} which can
* be declared on a field, test class, or test interface.
*
* <p>For concrete examples, see
* <p>For concrete examples of such composed annotations, see
* {@link org.springframework.test.context.bean.override.convention.TestBean @TestBean},
* {@link org.springframework.test.context.bean.override.mockito.MockitoBean @MockitoBean}, and
* {@link org.springframework.test.context.bean.override.mockito.MockitoSpyBean @MockitoSpyBean}.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* 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.
@ -104,11 +104,10 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
Set<String> generatedBeanNames) {
String beanName = handler.getBeanName();
Field field = handler.getField();
Assert.state(!BeanFactoryUtils.isFactoryDereference(beanName),() -> """
Unable to override bean '%s' for field '%s.%s': a FactoryBean cannot be overridden. \
To override the bean created by the FactoryBean, remove the '&' prefix.""".formatted(
beanName, field.getDeclaringClass().getSimpleName(), field.getName()));
Assert.state(!BeanFactoryUtils.isFactoryDereference(beanName), () -> """
Unable to override bean '%s'%s: a FactoryBean cannot be overridden. \
To override the bean created by the FactoryBean, remove the '&' prefix."""
.formatted(beanName, forField(handler.getField())));
switch (handler.getStrategy()) {
case REPLACE -> replaceOrCreateBean(beanFactory, handler, generatedBeanNames, true);
@ -134,7 +133,6 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
// 4) Create bean by-name, with a provided name
String beanName = handler.getBeanName();
Field field = handler.getField();
BeanDefinition existingBeanDefinition = null;
if (beanName == null) {
beanName = getBeanNameForType(beanFactory, handler, requireExistingBean);
@ -169,11 +167,10 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
existingBeanDefinition = beanFactory.getBeanDefinition(beanName);
}
else if (requireExistingBean) {
throw new IllegalStateException("""
Unable to replace bean: there is no bean with name '%s' and type %s \
(as required by field '%s.%s')."""
.formatted(beanName, handler.getBeanType(),
field.getDeclaringClass().getSimpleName(), field.getName()));
Field field = handler.getField();
throw new IllegalStateException(
"Unable to replace bean: there is no bean with name '%s' and type %s%s."
.formatted(beanName, handler.getBeanType(), requiredByField(field)));
}
// 4) We are creating a bean by-name with the provided beanName.
}
@ -264,13 +261,11 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
else {
String message = "Unable to select a bean to wrap: ";
if (candidateCount == 0) {
message += "there are no beans of type %s (as required by field '%s.%s')."
.formatted(beanType, field.getDeclaringClass().getSimpleName(), field.getName());
message += "there are no beans of type %s%s.".formatted(beanType, requiredByField(field));
}
else {
message += "found %d beans of type %s (as required by field '%s.%s'): %s"
.formatted(candidateCount, beanType, field.getDeclaringClass().getSimpleName(),
field.getName(), candidateNames);
message += "found %d beans of type %s%s: %s"
.formatted(candidateCount, beanType, requiredByField(field), candidateNames);
}
throw new IllegalStateException(message);
}
@ -281,11 +276,9 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
// We are wrapping an existing bean by-name.
Set<String> candidates = getExistingBeanNamesByType(beanFactory, handler, false);
if (!candidates.contains(beanName)) {
throw new IllegalStateException("""
Unable to wrap bean: there is no bean with name '%s' and type %s \
(as required by field '%s.%s')."""
.formatted(beanName, beanType, field.getDeclaringClass().getSimpleName(),
field.getName()));
throw new IllegalStateException(
"Unable to wrap bean: there is no bean with name '%s' and type %s%s."
.formatted(beanName, beanType, requiredByField(field)));
}
}
@ -308,8 +301,8 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
else if (candidateCount == 0) {
if (requireExistingBean) {
throw new IllegalStateException(
"Unable to override bean: there are no beans of type %s (as required by field '%s.%s')."
.formatted(beanType, field.getDeclaringClass().getSimpleName(), field.getName()));
"Unable to override bean: there are no beans of type %s%s."
.formatted(beanType, requiredByField(field)));
}
return null;
}
@ -320,14 +313,14 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
}
throw new IllegalStateException(
"Unable to select a bean to override: found %d beans of type %s (as required by field '%s.%s'): %s"
.formatted(candidateCount, beanType, field.getDeclaringClass().getSimpleName(),
field.getName(), candidateNames));
"Unable to select a bean to override: found %d beans of type %s%s: %s"
.formatted(candidateCount, beanType, requiredByField(field), candidateNames));
}
private Set<String> getExistingBeanNamesByType(ConfigurableListableBeanFactory beanFactory, BeanOverrideHandler handler,
boolean checkAutowiredCandidate) {
Field field = handler.getField();
ResolvableType resolvableType = handler.getBeanType();
Class<?> type = resolvableType.toClass();
@ -345,16 +338,16 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
}
// Filter out non-matching autowire candidates.
if (checkAutowiredCandidate) {
DependencyDescriptor descriptor = new DependencyDescriptor(handler.getField(), true);
if (field != null && checkAutowiredCandidate) {
DependencyDescriptor descriptor = new DependencyDescriptor(field, true);
beanNames.removeIf(beanName -> !beanFactory.isAutowireCandidate(beanName, descriptor));
}
// Filter out scoped proxy targets.
beanNames.removeIf(ScopedProxyUtils::isScopedTarget);
// In case of multiple matches, fall back on the field's name as a last resort.
if (beanNames.size() > 1) {
String fieldName = handler.getField().getName();
if (field != null && beanNames.size() > 1) {
String fieldName = field.getName();
if (beanNames.contains(fieldName)) {
return Set.of(fieldName);
}
@ -452,4 +445,19 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
dlbf.destroySingleton(beanName);
}
private static String forField(@Nullable Field field) {
if (field == null) {
return "";
}
return " for field '%s.%s'".formatted(field.getDeclaringClass().getSimpleName(), field.getName());
}
private static String requiredByField(@Nullable Field field) {
if (field == null) {
return "";
}
return " (as required by field '%s.%s')".formatted(
field.getDeclaringClass().getSimpleName(), field.getName());
}
}

View File

@ -23,7 +23,6 @@ import java.util.Set;
import org.springframework.lang.Nullable;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.TestContextAnnotationUtils;
import org.springframework.util.Assert;
/**
@ -52,10 +51,7 @@ class BeanOverrideContextCustomizerFactory implements ContextCustomizerFactory {
}
private void findBeanOverrideHandlers(Class<?> testClass, Set<BeanOverrideHandler> handlers) {
if (TestContextAnnotationUtils.searchEnclosingClass(testClass)) {
findBeanOverrideHandlers(testClass.getEnclosingClass(), handlers);
}
BeanOverrideHandler.forTestClass(testClass).forEach(handler ->
BeanOverrideHandler.findAllHandlers(testClass).forEach(handler ->
Assert.state(handlers.add(handler), () ->
"Duplicate BeanOverrideHandler discovered in test class %s: %s"
.formatted(testClass.getName(), handler)));

View File

@ -17,15 +17,18 @@
package org.springframework.test.context.bean.override;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.config.BeanDefinition;
@ -35,6 +38,7 @@ import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.style.ToStringCreator;
import org.springframework.lang.Nullable;
import org.springframework.test.context.TestContextAnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
@ -56,8 +60,8 @@ import static org.springframework.core.annotation.MergedAnnotations.SearchStrate
*
* <p>Concrete implementations of {@code BeanOverrideHandler} can store additional
* metadata to use during override {@linkplain #createOverrideInstance instance
* creation} &mdash; for example, based on further processing of the annotation
* or the annotated field.
* creation} &mdash; for example, based on further processing of the annotation,
* the annotated field, or the annotated class.
*
* <p><strong>NOTE</strong>: Only <em>singleton</em> beans can be overridden.
* Any attempt to override a non-singleton bean will result in an exception.
@ -69,6 +73,11 @@ import static org.springframework.core.annotation.MergedAnnotations.SearchStrate
*/
public abstract class BeanOverrideHandler {
private static final Comparator<MergedAnnotation<? extends Annotation>> reversedMetaDistance =
Comparator.<MergedAnnotation<? extends Annotation>> comparingInt(MergedAnnotation::getDistance).reversed();
@Nullable
private final Field field;
private final Set<Annotation> qualifierAnnotations;
@ -81,7 +90,7 @@ public abstract class BeanOverrideHandler {
private final BeanOverrideStrategy strategy;
protected BeanOverrideHandler(Field field, ResolvableType beanType, @Nullable String beanName,
protected BeanOverrideHandler(@Nullable Field field, ResolvableType beanType, @Nullable String beanName,
BeanOverrideStrategy strategy) {
this.field = field;
@ -95,57 +104,116 @@ public abstract class BeanOverrideHandler {
* Process the given {@code testClass} and build the corresponding
* {@code BeanOverrideHandler} list derived from {@link BeanOverride @BeanOverride}
* fields in the test class and its type hierarchy.
* <p>This method does not search the enclosing class hierarchy.
* <p>This method does not search the enclosing class hierarchy and does not
* search for {@code @BeanOverride} declarations on classes or interfaces.
* @param testClass the test class to process
* @return a list of bean override handlers
* @see org.springframework.test.context.TestContextAnnotationUtils#searchEnclosingClass(Class)
* @see #findAllHandlers(Class)
*/
public static List<BeanOverrideHandler> forTestClass(Class<?> testClass) {
List<BeanOverrideHandler> handlers = new LinkedList<>();
findHandlers(testClass, testClass, handlers);
return findHandlers(testClass, true);
}
/**
* Process the given {@code testClass} and build the corresponding
* {@code BeanOverrideHandler} list derived from {@link BeanOverride @BeanOverride}
* fields in the test class and in its type hierarchy as well as from
* {@code @BeanOverride} declarations on classes and interfaces.
* <p>This method additionally searches for {@code @BeanOverride} declarations
* in the enclosing class hierarchy based on
* {@link TestContextAnnotationUtils#searchEnclosingClass(Class)} semantics.
* @param testClass the test class to process
* @return a list of bean override handlers
* @since 6.2.2
*/
static List<BeanOverrideHandler> findAllHandlers(Class<?> testClass) {
return findHandlers(testClass, false);
}
private static List<BeanOverrideHandler> findHandlers(Class<?> testClass, boolean localFieldsOnly) {
List<BeanOverrideHandler> handlers = new ArrayList<>();
findHandlers(testClass, testClass, handlers, localFieldsOnly);
return handlers;
}
/**
* Find handlers using tail recursion to ensure that "locally declared"
* bean overrides take precedence over inherited bean overrides.
* Find handlers using tail recursion to ensure that "locally declared" bean overrides
* take precedence over inherited bean overrides.
* <p>Note: the search algorithm is effectively the inverse of the algorithm used in
* {@link org.springframework.test.context.TestContextAnnotationUtils#findAnnotationDescriptor(Class, Class)},
* but with tail recursion the semantics should be the same.
* @param clazz the class in/on which to search
* @param testClass the original test class
* @param handlers the list of handlers found
* @param localFieldsOnly whether to search only on local fields within the type hierarchy
* @since 6.2.2
*/
private static void findHandlers(Class<?> clazz, Class<?> testClass, List<BeanOverrideHandler> handlers) {
if (clazz == null || clazz == Object.class) {
return;
private static void findHandlers(Class<?> clazz, Class<?> testClass, List<BeanOverrideHandler> handlers,
boolean localFieldsOnly) {
// 1) Search enclosing class hierarchy.
if (!localFieldsOnly && TestContextAnnotationUtils.searchEnclosingClass(clazz)) {
findHandlers(clazz.getEnclosingClass(), testClass, handlers, localFieldsOnly);
}
// 1) Search type hierarchy.
findHandlers(clazz.getSuperclass(), testClass, handlers);
// 2) Search class hierarchy.
Class<?> superclass = clazz.getSuperclass();
if (superclass != null && superclass != Object.class) {
findHandlers(superclass, testClass, handlers, localFieldsOnly);
}
// 2) Process fields in current class.
if (!localFieldsOnly) {
// 3) Search interfaces.
for (Class<?> ifc : clazz.getInterfaces()) {
findHandlers(ifc, testClass, handlers, localFieldsOnly);
}
// 4) Process current class.
processClass(clazz, testClass, handlers);
}
// 5) Process fields in current class.
ReflectionUtils.doWithLocalFields(clazz, field -> processField(field, testClass, handlers));
}
private static void processClass(Class<?> clazz, Class<?> testClass, List<BeanOverrideHandler> handlers) {
processElement(clazz, testClass, (processor, composedAnnotation) ->
processor.createHandlers(composedAnnotation, testClass).forEach(handlers::add));
}
private static void processField(Field field, Class<?> testClass, List<BeanOverrideHandler> handlers) {
AtomicBoolean overrideAnnotationFound = new AtomicBoolean();
MergedAnnotations.from(field, DIRECT).stream(BeanOverride.class).forEach(mergedAnnotation -> {
processElement(field, testClass, (processor, composedAnnotation) -> {
Assert.state(!Modifier.isStatic(field.getModifiers()),
() -> "@BeanOverride field must not be static: " + field);
MergedAnnotation<?> metaSource = mergedAnnotation.getMetaSource();
Assert.state(metaSource != null, "@BeanOverride annotation must be meta-present");
BeanOverride beanOverride = mergedAnnotation.synthesize();
BeanOverrideProcessor processor = BeanUtils.instantiateClass(beanOverride.value());
Annotation composedAnnotation = metaSource.synthesize();
Assert.state(overrideAnnotationFound.compareAndSet(false, true),
() -> "Multiple @BeanOverride annotations found on field: " + field);
BeanOverrideHandler handler = processor.createHandler(composedAnnotation, testClass, field);
handlers.add(handler);
handlers.add(processor.createHandler(composedAnnotation, testClass, field));
});
}
private static void processElement(AnnotatedElement element, Class<?> testClass,
BiConsumer<BeanOverrideProcessor, Annotation> consumer) {
MergedAnnotations.from(element, DIRECT)
.stream(BeanOverride.class)
.sorted(reversedMetaDistance)
.forEach(mergedAnnotation -> {
MergedAnnotation<?> metaSource = mergedAnnotation.getMetaSource();
Assert.state(metaSource != null, "@BeanOverride annotation must be meta-present");
BeanOverride beanOverride = mergedAnnotation.synthesize();
BeanOverrideProcessor processor = BeanUtils.instantiateClass(beanOverride.value());
Annotation composedAnnotation = metaSource.synthesize();
consumer.accept(processor, composedAnnotation);
});
}
/**
* Get the annotated {@link Field}.
*/
@Nullable
public final Field getField() {
return this.field;
}
@ -249,7 +317,10 @@ public abstract class BeanOverrideHandler {
}
// by-type lookup
return (Objects.equals(this.field.getName(), that.field.getName()) &&
if (this.field == null) {
return (that.field == null);
}
return (that.field != null && this.field.getName().equals(that.field.getName()) &&
this.qualifierAnnotations.equals(that.qualifierAnnotations));
}
@ -257,7 +328,7 @@ public abstract class BeanOverrideHandler {
public int hashCode() {
int hash = Objects.hash(getClass(), this.beanType.getType(), this.beanName, this.strategy);
return (this.beanName != null ? hash : hash +
Objects.hash(this.field.getName(), this.qualifierAnnotations));
Objects.hash((this.field != null ? this.field.getName() : null), this.qualifierAnnotations));
}
@Override
@ -271,7 +342,10 @@ public abstract class BeanOverrideHandler {
}
private static Set<Annotation> getQualifierAnnotations(Field field) {
private static Set<Annotation> getQualifierAnnotations(@Nullable Field field) {
if (field == null) {
return Collections.emptySet();
}
Annotation[] candidates = field.getDeclaredAnnotations();
if (candidates.length == 0) {
return Collections.emptySet();

View File

@ -18,10 +18,13 @@ package org.springframework.test.context.bean.override;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.List;
/**
* Strategy interface for Bean Override processing, which creates a
* {@link BeanOverrideHandler} that drives how the target bean is overridden.
* Strategy interface for Bean Override processing, which creates
* {@link BeanOverrideHandler} instances that drive how target beans are
* overridden.
*
* <p>At least one composed annotation that is meta-annotated with
* {@link BeanOverride @BeanOverride} must be a companion of this processor and
@ -40,12 +43,41 @@ public interface BeanOverrideProcessor {
/**
* Create a {@link BeanOverrideHandler} for the given annotated field.
* <p>This method will only be invoked when a {@link BeanOverride @BeanOverride}
* annotation is declared on a field &mdash; for example, if the supplied field
* is annotated with {@code @MockitoBean}.
* @param overrideAnnotation the composed annotation that declares the
* {@link BeanOverride @BeanOverride} annotation which registers this processor
* {@code @BeanOverride} annotation which registers this processor
* @param testClass the test class to process
* @param field the annotated field
* @return the {@code BeanOverrideHandler} that should handle the given field
* @see #createHandlers(Annotation, Class)
*/
BeanOverrideHandler createHandler(Annotation overrideAnnotation, Class<?> testClass, Field field);
/**
* Create a list of {@link BeanOverrideHandler} instances for the given override
* annotation and test class.
* <p>This method will only be invoked when a {@link BeanOverride @BeanOverride}
* annotation is declared at the type level &mdash; for example, if the supplied
* test class is annotated with {@code @MockitoBean}.
* <p>Note that the test class may not be directly annotated with the override
* annotation. For example, the override annotation may have been declared
* on an interface, superclass, or enclosing class within the test class
* hierarchy.
* <p>The default implementation returns an empty list, signaling that this
* {@code BeanOverrideProcessor} does not support type-level {@code @BeanOverride}
* declarations. Can be overridden by concrete implementations to support
* type-level use cases.
* @param overrideAnnotation the composed annotation that declares the
* {@code @BeanOverride} annotation which registers this processor
* @param testClass the test class to process
* @return the list of {@code BeanOverrideHandlers} for the annotated class
* @since 6.2.2
* @see #createHandler(Annotation, Class, Field)
*/
default List<BeanOverrideHandler> createHandlers(Annotation overrideAnnotation, Class<?> testClass) {
return Collections.emptyList();
}
}

View File

@ -108,9 +108,11 @@ class BeanOverrideRegistry {
}
void inject(Object target, BeanOverrideHandler handler) {
Field field = handler.getField();
Assert.notNull(field, () -> "BeanOverrideHandler must have a non-null field: " + handler);
String beanName = this.handlerToBeanNameMap.get(handler);
Assert.state(StringUtils.hasLength(beanName), () -> "No bean found for BeanOverrideHandler: " + handler);
inject(handler.getField(), target, beanName);
inject(field, target, beanName);
}
private void inject(Field field, Object target, String beanName) {

View File

@ -38,7 +38,7 @@ abstract class AbstractMockitoBeanOverrideHandler extends BeanOverrideHandler {
private final MockReset reset;
protected AbstractMockitoBeanOverrideHandler(Field field, ResolvableType beanType,
protected AbstractMockitoBeanOverrideHandler(@Nullable Field field, ResolvableType beanType,
@Nullable String beanName, BeanOverrideStrategy strategy, @Nullable MockReset reset) {
super(field, beanType, beanName, strategy);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* 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.
@ -18,6 +18,7 @@ package org.springframework.test.context.bean.override.mockito;
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;
@ -29,17 +30,37 @@ import org.springframework.core.annotation.AliasFor;
import org.springframework.test.context.bean.override.BeanOverride;
/**
* {@code @MockitoBean} is an annotation that can be applied to a non-static field
* in a test class to override a bean in the test's
* {@code @MockitoBean} is an annotation that can be used in test classes to
* override beans in a test's
* {@link org.springframework.context.ApplicationContext ApplicationContext}
* using a Mockito mock.
* using Mockito mocks.
*
* <p>By default, the bean to mock 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 mock by setting the
* {@link #value() value} or {@link #name() name} attribute.
* <p>{@code @MockitoBean} can be applied in the following ways.
* <ul>
* <li>On a non-static field in a test class or any of its superclasses.</li>
* <li>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.</li>
* <li>At the type level on a test class or any superclass or implemented interface
* in the type hierarchy above the test class.</li>
* <li>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.</li>
* </ul>
*
* <p>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.
*
* <p>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.
*
* <p>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,
@ -63,19 +84,29 @@ import org.springframework.test.context.bean.override.BeanOverride;
* (default visibility), or {@code private} depending on the needs or coding
* practices of the project.
*
* <p>{@code @MockitoBean} fields will be inherited from an enclosing test class by default.
* See {@link org.springframework.test.context.NestedTestConfiguration @NestedTestConfiguration}
* <p>{@code @MockitoBean} fields and type-level {@code @MockitoBean} declarations
* will be inherited from an enclosing test class by default. See
* {@link org.springframework.test.context.NestedTestConfiguration @NestedTestConfiguration}
* for details.
*
* <p>{@code @MockitoBean} may be used as a <em>meta-annotation</em> to create custom
* <em>composed annotations</em> &mdash; for example, to define common mock
* configuration in a single annotation that can be reused across a test suite.
* {@code @MockitoBean} can also be used as a <em>{@linkplain Repeatable repeatable}</em>
* annotation at the type level &mdash; for example, to mock several beans by
* {@link #name() name}.
*
* @author Simon Baslé
* @author Sam Brannen
* @since 6.2
* @see org.springframework.test.context.bean.override.mockito.MockitoBeans @MockitoBeans
* @see org.springframework.test.context.bean.override.mockito.MockitoSpyBean @MockitoSpyBean
* @see org.springframework.test.context.bean.override.convention.TestBean @TestBean
*/
@Target(ElementType.FIELD)
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(MockitoBeans.class)
@BeanOverride(MockitoBeanOverrideProcessor.class)
public @interface MockitoBean {
@ -91,13 +122,27 @@ public @interface MockitoBean {
/**
* Name of the bean to mock.
* <p>If left unspecified, the bean to mock is selected according to the
* annotated field's type, taking qualifiers into account if necessary. See
* the {@linkplain MockitoBean class-level documentation} for details.
* configured {@link #types() types} or the annotated field's type, taking
* qualifiers into account if necessary. See the {@linkplain MockitoBean
* class-level documentation} for details.
* @see #value()
*/
@AliasFor("value")
String name() default "";
/**
* One or more types to mock.
* <p>Defaults to none.
* <p>Each type specified will result in a mock being created and registered
* with the {@code ApplicationContext}.
* <p>Types must be omitted when the annotation is used on a field.
* <p>When {@code @MockitoBean} also defines a {@link #name}, this attribute
* can only contain a single value.
* @return the types to mock
* @since 6.2.2
*/
Class<?>[] types() default {};
/**
* Extra interfaces that should also be declared by the mock.
* <p>Defaults to none.

View File

@ -57,13 +57,17 @@ class MockitoBeanOverrideHandler extends AbstractMockitoBeanOverrideHandler {
private final boolean serializable;
MockitoBeanOverrideHandler(Field field, ResolvableType typeToMock, MockitoBean mockitoBean) {
MockitoBeanOverrideHandler(ResolvableType typeToMock, MockitoBean mockitoBean) {
this(null, typeToMock, mockitoBean);
}
MockitoBeanOverrideHandler(@Nullable Field field, ResolvableType typeToMock, MockitoBean mockitoBean) {
this(field, typeToMock, (!mockitoBean.name().isBlank() ? mockitoBean.name() : null),
(mockitoBean.enforceOverride() ? REPLACE : REPLACE_OR_CREATE),
mockitoBean.reset(), mockitoBean.extraInterfaces(), mockitoBean.answers(), mockitoBean.serializable());
}
private MockitoBeanOverrideHandler(Field field, ResolvableType typeToMock, @Nullable String beanName,
private MockitoBeanOverrideHandler(@Nullable Field field, ResolvableType typeToMock, @Nullable String beanName,
BeanOverrideStrategy strategy, MockReset reset, Class<?>[] extraInterfaces, Answers answers,
boolean serializable) {

View File

@ -18,15 +18,20 @@ package org.springframework.test.context.bean.override.mockito;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import org.springframework.core.ResolvableType;
import org.springframework.test.context.bean.override.BeanOverrideHandler;
import org.springframework.test.context.bean.override.BeanOverrideProcessor;
import org.springframework.util.Assert;
/**
* {@link BeanOverrideProcessor} implementation that provides support for
* {@link MockitoBean @MockitoBean} and {@link MockitoSpyBean @MockitoSpyBean}.
*
* @author Simon Baslé
* @author Sam Brannen
* @since 6.2
* @see MockitoBean @MockitoBean
* @see MockitoSpyBean @MockitoSpyBean
@ -36,6 +41,8 @@ class MockitoBeanOverrideProcessor implements BeanOverrideProcessor {
@Override
public AbstractMockitoBeanOverrideHandler createHandler(Annotation overrideAnnotation, Class<?> testClass, Field field) {
if (overrideAnnotation instanceof MockitoBean mockitoBean) {
Assert.state(mockitoBean.types().length == 0,
"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) {
@ -47,4 +54,23 @@ class MockitoBeanOverrideProcessor implements BeanOverrideProcessor {
.formatted(field.getDeclaringClass().getName(), field.getName()));
}
@Override
public List<BeanOverrideHandler> 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());
}
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<BeanOverrideHandler> handlers = new ArrayList<>();
for (Class<?> type : types) {
handlers.add(new MockitoBeanOverrideHandler(ResolvableType.forClass(type), mockitoBean));
}
return handlers;
}
}

View File

@ -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 MockitoBean @MockitoBean} annotations which allows
* {@code @MockitoBean} to be used as a {@linkplain java.lang.annotation.Repeatable
* repeatable annotation} at the type level &mdash; for example, on test classes
* or interfaces implemented by test classes.
*
* @author Sam Brannen
* @since 6.2.2
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MockitoBeans {
MockitoBean[] value();
}

View File

@ -30,4 +30,8 @@ public abstract class BeanOverrideTestUtils {
return BeanOverrideHandler.forTestClass(testClass);
}
public static List<BeanOverrideHandler> findAllHandlers(Class<?> testClass) {
return BeanOverrideHandler.findAllHandlers(testClass);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* 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.
@ -67,6 +67,76 @@ class MockitoBeanOverrideHandlerTests {
assertThat(handler1).hasSameHashCodeAs(handler2);
}
@Test // gh-33925
void isEqualToWithSameInstanceFromClassLevel() {
MockitoBeanOverrideHandler handler1 = createHandler(ClassLevelStringMockByName1.class);
assertThat(handler1).isEqualTo(handler1);
assertThat(handler1).hasSameHashCodeAs(handler1);
MockitoBeanOverrideHandler handler2 = createHandler(ClassLevelStringMockByType1.class);
assertThat(handler2).isEqualTo(handler2);
assertThat(handler2).hasSameHashCodeAs(handler2);
}
@Test // gh-33925
void isEqualToWithSameByNameLookupMetadataFromClassLevel() {
MockitoBeanOverrideHandler handler1 = createHandler(ClassLevelStringMockByName1.class);
MockitoBeanOverrideHandler handler2 = createHandler(ClassLevelStringMockByName2.class);
assertThat(handler1).isEqualTo(handler2);
assertThat(handler2).isEqualTo(handler1);
assertThat(handler1).hasSameHashCodeAs(handler2);
}
@Test // gh-33925
void isNotEqualToWithDifferentByNameLookupMetadataFromClassLevel() {
MockitoBeanOverrideHandler handler1 = createHandler(ClassLevelStringMockByName1.class);
MockitoBeanOverrideHandler handler2 = createHandler(ClassLevelStringMockByName3.class);
assertThat(handler1).isNotEqualTo(handler2);
assertThat(handler2).isNotEqualTo(handler1);
assertThat(handler1).doesNotHaveSameHashCodeAs(handler2);
}
@Test // gh-33925
void isEqualToWithSameByTypeLookupMetadataFromClassLevel() {
MockitoBeanOverrideHandler handler1 = createHandler(ClassLevelStringMockByType1.class);
MockitoBeanOverrideHandler handler2 = createHandler(ClassLevelStringMockByType2.class);
assertThat(handler1).isEqualTo(handler2);
assertThat(handler2).isEqualTo(handler1);
assertThat(handler1).hasSameHashCodeAs(handler2);
}
@Test // gh-33925
void isNotEqualToWithDifferentByTypeLookupMetadataFromClassLevel() {
MockitoBeanOverrideHandler handler1 = createHandler(ClassLevelStringMockByType1.class);
MockitoBeanOverrideHandler handler2 = createHandler(ClassLevelStringMockByType3.class);
assertThat(handler1).isNotEqualTo(handler2);
assertThat(handler2).isNotEqualTo(handler1);
assertThat(handler1).doesNotHaveSameHashCodeAs(handler2);
}
@Test // gh-33925
void isEqualToWithSameByNameLookupMetadataFromFieldAndClassLevel() {
MockitoBeanOverrideHandler handler1 = createHandler(sampleField("service3"));
MockitoBeanOverrideHandler handler2 = createHandler(ClassLevelStringMockByName1.class);
assertThat(handler1).isEqualTo(handler2);
assertThat(handler2).isEqualTo(handler1);
assertThat(handler1).hasSameHashCodeAs(handler2);
}
/**
* Since the "field name as fallback qualifier" is not available for an annotated class,
* what would seem to be "equivalent" handlers are actually not considered "equal" when
* the the lookup is "by type".
*/
@Test // gh-33925
void isNotEqualToWithSameByTypeLookupMetadataFromFieldAndClassLevel() {
MockitoBeanOverrideHandler handler1 = createHandler(sampleField("service"));
MockitoBeanOverrideHandler handler2 = createHandler(ClassLevelStringMockByType1.class);
assertThat(handler1).isNotEqualTo(handler2);
assertThat(handler2).isNotEqualTo(handler1);
assertThat(handler1).doesNotHaveSameHashCodeAs(handler2);
}
@Test
void isNotEqualEqualToByTypeLookupWithSameMetadataButDifferentField() {
MockitoBeanOverrideHandler handler1 = createHandler(sampleField("service"));
@ -122,6 +192,11 @@ class MockitoBeanOverrideHandlerTests {
return new MockitoBeanOverrideHandler(field, ResolvableType.forClass(field.getType()), annotation);
}
private MockitoBeanOverrideHandler createHandler(Class<?> clazz) {
MockitoBean annotation = AnnotatedElementUtils.getMergedAnnotation(clazz, MockitoBean.class);
return new MockitoBeanOverrideHandler(null, ResolvableType.forClass(annotation.types()[0]), annotation);
}
static class SampleOneMock {
@ -159,4 +234,28 @@ class MockitoBeanOverrideHandlerTests {
private String service7;
}
@MockitoBean(name = "beanToMock", types = String.class)
static class ClassLevelStringMockByName1 {
}
@MockitoBean(name = "beanToMock", types = String.class)
static class ClassLevelStringMockByName2 {
}
@MockitoBean(name = "otherBeanToMock", types = String.class)
static class ClassLevelStringMockByName3 {
}
@MockitoBean(types = String.class)
static class ClassLevelStringMockByType1 {
}
@MockitoBean(types = String.class)
static class ClassLevelStringMockByType2 {
}
@MockitoBean(types = Integer.class)
static class ClassLevelStringMockByType3 {
}
}

View File

@ -18,7 +18,9 @@ package org.springframework.test.context.bean.override.mockito;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.List;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.core.annotation.AnnotationUtils;
@ -41,43 +43,155 @@ class MockitoBeanOverrideProcessorTests {
private final MockitoBeanOverrideProcessor processor = new MockitoBeanOverrideProcessor();
private final Field field = ReflectionUtils.findField(TestCase.class, "number");
@Nested
class CreateHandlerTests {
private final Field field = ReflectionUtils.findField(TestCase.class, "number");
@Test
void mockAnnotationCreatesMockitoBeanOverrideHandler() {
MockitoBean annotation = AnnotationUtils.synthesizeAnnotation(MockitoBean.class);
BeanOverrideHandler object = processor.createHandler(annotation, TestCase.class, field);
@Test
void mockAnnotationCreatesMockitoBeanOverrideHandler() {
MockitoBean annotation = AnnotationUtils.synthesizeAnnotation(MockitoBean.class);
BeanOverrideHandler object = processor.createHandler(annotation, TestCase.class, field);
assertThat(object).isExactlyInstanceOf(MockitoBeanOverrideHandler.class);
assertThat(object).isExactlyInstanceOf(MockitoBeanOverrideHandler.class);
}
@Test
void spyAnnotationCreatesMockitoSpyBeanOverrideHandler() {
MockitoSpyBean annotation = AnnotationUtils.synthesizeAnnotation(MockitoSpyBean.class);
BeanOverrideHandler object = processor.createHandler(annotation, TestCase.class, field);
assertThat(object).isExactlyInstanceOf(MockitoSpyBeanOverrideHandler.class);
}
@Test
void otherAnnotationThrows() {
Annotation annotation = field.getAnnotation(Nullable.class);
assertThatIllegalStateException()
.isThrownBy(() -> processor.createHandler(annotation, TestCase.class, field))
.withMessage("Invalid annotation passed to MockitoBeanOverrideProcessor: expected either " +
"@MockitoBean or @MockitoSpyBean on field %s.%s", field.getDeclaringClass().getName(),
field.getName());
}
@Test
void typesNotSupportedAtFieldLevel() {
Field field = ReflectionUtils.findField(TestCase.class, "typesNotSupported");
MockitoBean annotation = field.getAnnotation(MockitoBean.class);
assertThatIllegalStateException()
.isThrownBy(() -> processor.createHandler(annotation, TestCase.class, field))
.withMessage("The @MockitoBean 'types' attribute must be omitted when declared on a field");
}
static class TestCase {
@Nullable
@MockitoBean
@MockitoSpyBean
Integer number;
@MockitoBean(types = Integer.class)
String typesNotSupported;
}
@MockitoBean(name = "bogus", types = Integer.class)
static class NameNotSupportedTestCase {
}
}
@Test
void spyAnnotationCreatesMockitoSpyBeanOverrideHandler() {
MockitoSpyBean annotation = AnnotationUtils.synthesizeAnnotation(MockitoSpyBean.class);
BeanOverrideHandler object = processor.createHandler(annotation, TestCase.class, field);
@Nested
class CreateHandlersTests {
assertThat(object).isExactlyInstanceOf(MockitoSpyBeanOverrideHandler.class);
}
@Test
void missingTypes() {
Class<?> testClass = MissingTypesTestCase.class;
MockitoBean annotation = testClass.getAnnotation(MockitoBean.class);
@Test
void otherAnnotationThrows() {
Annotation annotation = field.getAnnotation(Nullable.class);
assertThatIllegalStateException()
.isThrownBy(() -> processor.createHandlers(annotation, testClass))
.withMessage("The @MockitoBean 'types' attribute must not be empty when declared on a class");
}
assertThatIllegalStateException()
.isThrownBy(() -> processor.createHandler(annotation, TestCase.class, field))
.withMessage("Invalid annotation passed to MockitoBeanOverrideProcessor: expected either " +
"@MockitoBean or @MockitoSpyBean on field %s.%s", field.getDeclaringClass().getName(),
field.getName());
}
@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<BeanOverrideHandler> 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<BeanOverrideHandler> 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<BeanOverrideHandler> 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);
}
);
}
static class TestCase {
@Nullable
@MockitoBean
@MockitoSpyBean
Integer number;
static class MissingTypesTestCase {
}
@MockitoBean(name = "bogus", types = { Integer.class, Float.class })
static class NameNotSupportedWithMultipleTypesTestCase {
}
@MockitoBean(types = Integer.class)
static class SingleMockByTypeTestCase {
}
@MockitoBean(name = "enigma", types = Integer.class)
static class SingleMockByNameTestCase {
}
@MockitoBean(types = { Integer.class, Float.class })
static class MultipleMocksTestCase {
}
}
}

View File

@ -0,0 +1,102 @@
/*
* 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.mockbeans;
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.MockitoBean;
import org.springframework.test.context.bean.override.mockito.MockitoBeans;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
/**
* Integration tests for {@link MockitoBeans @MockitoBeans} and
* {@link MockitoBean @MockitoBean} declared "by name" at the class level as a
* repeatable annotation.
*
* @author Sam Brannen
* @since 6.2.2
* @see <a href="https://github.com/spring-projects/spring-framework/issues/33925">gh-33925</a>
* @see MockitoBeansByTypeIntegrationTests
*/
@SpringJUnitConfig
@MockitoBean(name = "s1", types = ExampleService.class)
@MockitoBean(name = "s2", types = ExampleService.class)
class MockitoBeansByNameIntegrationTests {
@Autowired
ExampleService s1;
@Autowired
ExampleService s2;
@MockitoBean(name = "s3")
ExampleService service3;
@Autowired
@Qualifier("s4")
ExampleService service4;
@BeforeEach
void configureMocks() {
given(s1.greeting()).willReturn("mock 1");
given(s2.greeting()).willReturn("mock 2");
given(service3.greeting()).willReturn("mock 3");
}
@Test
void checkMocksAndStandardBean() {
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 {
@Bean
ExampleService s1() {
return () -> "prod 1";
}
@Bean
ExampleService s2() {
return () -> "prod 2";
}
@Bean
ExampleService s3() {
return () -> "prod 3";
}
@Bean
ExampleService s4() {
return () -> "prod 4";
}
}
}

View File

@ -0,0 +1,147 @@
/*
* 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.mockbeans;
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.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.context.bean.override.mockito.MockitoBeans;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
/**
* Integration tests for {@link MockitoBeans @MockitoBeans} and
* {@link MockitoBean @MockitoBean} declared "by type" at the class level, as a
* repeatable annotation, and via a custom composed annotation.
*
* @author Sam Brannen
* @since 6.2.2
* @see <a href="https://github.com/spring-projects/spring-framework/issues/33925">gh-33925</a>
* @see MockitoBeansByNameIntegrationTests
*/
@SpringJUnitConfig
@MockitoBean(types = {Service04.class, Service05.class})
@SharedMocks // Intentionally declared between local @MockitoBean declarations
@MockitoBean(types = Service06.class)
class MockitoBeansByTypeIntegrationTests implements TestInterface01 {
@Autowired
Service01 service01;
@Autowired
Service02 service02;
@Autowired
Service03 service03;
@Autowired
Service04 service04;
@Autowired
Service05 service05;
@Autowired
Service06 service06;
@MockitoBean
Service07 service07;
@BeforeEach
void configureMocks() {
given(service01.greeting()).willReturn("mock 01");
given(service02.greeting()).willReturn("mock 02");
given(service03.greeting()).willReturn("mock 03");
given(service04.greeting()).willReturn("mock 04");
given(service05.greeting()).willReturn("mock 05");
given(service06.greeting()).willReturn("mock 06");
given(service07.greeting()).willReturn("mock 07");
}
@Test
void checkMocks() {
assertThat(service01.greeting()).isEqualTo("mock 01");
assertThat(service02.greeting()).isEqualTo("mock 02");
assertThat(service03.greeting()).isEqualTo("mock 03");
assertThat(service04.greeting()).isEqualTo("mock 04");
assertThat(service05.greeting()).isEqualTo("mock 05");
assertThat(service06.greeting()).isEqualTo("mock 06");
assertThat(service07.greeting()).isEqualTo("mock 07");
}
@MockitoBean(types = Service09.class)
static class BaseTestCase implements TestInterface08 {
@Autowired
Service08 service08;
@Autowired
Service09 service09;
@MockitoBean
Service10 service10;
}
@Nested
@MockitoBean(types = Service12.class)
class NestedTests extends BaseTestCase implements TestInterface11 {
@Autowired
Service11 service11;
@Autowired
Service12 service12;
@MockitoBean
Service13 service13;
@BeforeEach
void configureMocks() {
given(service08.greeting()).willReturn("mock 08");
given(service09.greeting()).willReturn("mock 09");
given(service10.greeting()).willReturn("mock 10");
given(service11.greeting()).willReturn("mock 11");
given(service12.greeting()).willReturn("mock 12");
given(service13.greeting()).willReturn("mock 13");
}
@Test
void checkMocks() {
assertThat(service01.greeting()).isEqualTo("mock 01");
assertThat(service02.greeting()).isEqualTo("mock 02");
assertThat(service03.greeting()).isEqualTo("mock 03");
assertThat(service04.greeting()).isEqualTo("mock 04");
assertThat(service05.greeting()).isEqualTo("mock 05");
assertThat(service06.greeting()).isEqualTo("mock 06");
assertThat(service07.greeting()).isEqualTo("mock 07");
assertThat(service08.greeting()).isEqualTo("mock 08");
assertThat(service09.greeting()).isEqualTo("mock 09");
assertThat(service10.greeting()).isEqualTo("mock 10");
assertThat(service11.greeting()).isEqualTo("mock 11");
assertThat(service12.greeting()).isEqualTo("mock 12");
assertThat(service13.greeting()).isEqualTo("mock 13");
}
}
}

View File

@ -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.mockbeans;
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.MockitoBean;
import org.springframework.test.context.bean.override.mockito.MockitoBeans;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link MockitoBeans @MockitoBeans}: {@link MockitoBean @MockitoBean}
* declared at the class level, as a repeatable annotation, and via a custom composed
* annotation.
*
* @author Sam Brannen
* @since 6.2.2
* @see <a href="https://github.com/spring-projects/spring-framework/issues/33925">gh-33925</a>
*/
class MockitoBeansTests {
@Test
void registrationOrderForTopLevelClass() {
Stream<Class<?>> mockedServices = getRegisteredMockTypes(MockitoBeansByTypeIntegrationTests.class);
assertThat(mockedServices).containsExactly(
Service01.class, Service02.class, Service03.class, Service04.class,
Service05.class, Service06.class, Service07.class);
}
@Test
void registrationOrderForNestedClass() {
Stream<Class<?>> mockedServices = getRegisteredMockTypes(MockitoBeansByTypeIntegrationTests.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<Class<?>> getRegisteredMockTypes(Class<?> testClass) {
return BeanOverrideTestUtils.findAllHandlers(testClass)
.stream()
.map(BeanOverrideHandler::getBeanType)
.map(ResolvableType::getRawClass);
}
}

View File

@ -0,0 +1,21 @@
/*
* 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.mockbeans;
interface Service {
String greeting();
}

View File

@ -0,0 +1,20 @@
/*
* 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.mockbeans;
interface Service01 extends Service {
}

View File

@ -0,0 +1,20 @@
/*
* 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.mockbeans;
interface Service02 extends Service {
}

View File

@ -0,0 +1,20 @@
/*
* 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.mockbeans;
interface Service03 extends Service {
}

View File

@ -0,0 +1,20 @@
/*
* 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.mockbeans;
interface Service04 extends Service {
}

View File

@ -0,0 +1,20 @@
/*
* 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.mockbeans;
interface Service05 extends Service {
}

View File

@ -0,0 +1,20 @@
/*
* 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.mockbeans;
interface Service06 extends Service {
}

View File

@ -0,0 +1,20 @@
/*
* 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.mockbeans;
interface Service07 extends Service {
}

View File

@ -0,0 +1,20 @@
/*
* 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.mockbeans;
interface Service08 extends Service {
}

View File

@ -0,0 +1,20 @@
/*
* 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.mockbeans;
interface Service09 extends Service {
}

View File

@ -0,0 +1,20 @@
/*
* 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.mockbeans;
interface Service10 extends Service {
}

View File

@ -0,0 +1,20 @@
/*
* 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.mockbeans;
interface Service11 extends Service {
}

View File

@ -0,0 +1,20 @@
/*
* 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.mockbeans;
interface Service12 extends Service {
}

View File

@ -0,0 +1,20 @@
/*
* 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.mockbeans;
interface Service13 extends Service {
}

View File

@ -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.mockbeans;
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.MockitoBean;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@MockitoBean(types = Service02.class)
@MockitoBean(types = Service03.class)
@interface SharedMocks {
}

View File

@ -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.mockbeans;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
@MockitoBean(types = Service01.class)
interface TestInterface01 {
}

View File

@ -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.mockbeans;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
@MockitoBean(types = Service08.class)
interface TestInterface08 {
}

View File

@ -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.mockbeans;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
@MockitoBean(types = Service11.class)
interface TestInterface11 {
}