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:
parent
8b6523a35b
commit
9181cce65f
|
@ -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`.
|
||||
|
|
|
@ -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}.
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
|
|
|
@ -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} — for example, based on further processing of the annotation
|
||||
* or the annotated field.
|
||||
* creation} — 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();
|
||||
|
|
|
@ -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 — 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 — 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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> — 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 — 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.
|
||||
|
|
|
@ -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) {
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 — 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();
|
||||
|
||||
}
|
|
@ -30,4 +30,8 @@ public abstract class BeanOverrideTestUtils {
|
|||
return BeanOverrideHandler.forTestClass(testClass);
|
||||
}
|
||||
|
||||
public static List<BeanOverrideHandler> findAllHandlers(Class<?> testClass) {
|
||||
return BeanOverrideHandler.findAllHandlers(testClass);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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 {
|
||||
}
|
Loading…
Reference in New Issue