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
|
override. Alternatively, a candidate whose bean name matches the name of the field will
|
||||||
match.
|
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]
|
[WARNING]
|
||||||
====
|
====
|
||||||
Qualifiers, including the name of the field, are used to determine if a separate
|
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
|
`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.
|
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].
|
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,
|
If no existing bean matches, a new bean is created on the fly. However, you can switch to
|
||||||
you can switch to the `REPLACE` strategy by setting the `enforceOverride` attribute to
|
the `REPLACE` strategy by setting the `enforceOverride` attribute to `true`. See the
|
||||||
`true`.
|
following section for an example.
|
||||||
|
|
||||||
The `@MockitoSpyBean` annotation uses the `WRAP`
|
The `@MockitoSpyBean` annotation uses the `WRAP`
|
||||||
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy],
|
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-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.
|
or `private` depending on the needs or coding practices of the project.
|
||||||
====
|
====
|
||||||
|
|
||||||
|
[[spring-testing-annotation-beanoverriding-mockitobean-examples]]
|
||||||
|
== `@MockitoBean` Examples
|
||||||
|
|
||||||
|
When using `@MockitoBean`, a new bean will be created if a corresponding bean does not
|
||||||
|
exist. However, if you would like for the test to fail when a corresponding bean does not
|
||||||
|
exist, you can set the `enforceOverride` attribute to `true` – for example,
|
||||||
|
`@MockitoBean(enforceOverride = true)`.
|
||||||
|
|
||||||
|
To use a by-name override rather than a by-type override, specify the `name` (or `value`)
|
||||||
|
attribute of the annotation.
|
||||||
|
|
||||||
The following example shows how to use the default behavior of the `@MockitoBean` annotation:
|
The following example shows how to use the default behavior of the `@MockitoBean` annotation:
|
||||||
|
|
||||||
[tabs]
|
[tabs]
|
||||||
|
@ -69,11 +72,13 @@ Java::
|
||||||
+
|
+
|
||||||
[source,java,indent=0,subs="verbatim,quotes"]
|
[source,java,indent=0,subs="verbatim,quotes"]
|
||||||
----
|
----
|
||||||
class OverrideBeanTests {
|
@SpringJUnitConfig(TestConfig.class)
|
||||||
|
class BeanOverrideTests {
|
||||||
|
|
||||||
@MockitoBean // <1>
|
@MockitoBean // <1>
|
||||||
CustomService customService;
|
CustomService customService;
|
||||||
|
|
||||||
// test case body...
|
// tests...
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
<1> Replace the bean with type `CustomService` with a Mockito `mock`.
|
<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
|
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
|
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
|
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
|
`CustomService` beans you want to override. If no such bean exists, a bean will be
|
||||||
will be created with an auto-generated bean name.
|
created with an auto-generated bean name.
|
||||||
|
|
||||||
The following example uses a by-name lookup, rather than a by-type lookup:
|
The following example uses a by-name lookup, rather than a by-type lookup:
|
||||||
|
|
||||||
|
@ -93,20 +98,43 @@ Java::
|
||||||
+
|
+
|
||||||
[source,java,indent=0,subs="verbatim,quotes"]
|
[source,java,indent=0,subs="verbatim,quotes"]
|
||||||
----
|
----
|
||||||
class OverrideBeanTests {
|
@SpringJUnitConfig(TestConfig.class)
|
||||||
|
class BeanOverrideTests {
|
||||||
|
|
||||||
@MockitoBean("service") // <1>
|
@MockitoBean("service") // <1>
|
||||||
CustomService customService;
|
CustomService customService;
|
||||||
|
|
||||||
// test case body...
|
// tests...
|
||||||
|
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
<1> Replace the bean named `service` with a Mockito `mock`.
|
<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]
|
[tabs]
|
||||||
======
|
======
|
||||||
|
@ -114,11 +142,70 @@ Java::
|
||||||
+
|
+
|
||||||
[source,java,indent=0,subs="verbatim,quotes"]
|
[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>
|
@MockitoSpyBean // <1>
|
||||||
CustomService customService;
|
CustomService customService;
|
||||||
|
|
||||||
// test case body...
|
// tests...
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
<1> Wrap the bean with type `CustomService` with a Mockito `spy`.
|
<1> Wrap the bean with type `CustomService` with a Mockito `spy`.
|
||||||
|
@ -137,12 +224,13 @@ Java::
|
||||||
+
|
+
|
||||||
[source,java,indent=0,subs="verbatim,quotes"]
|
[source,java,indent=0,subs="verbatim,quotes"]
|
||||||
----
|
----
|
||||||
class OverrideBeanTests {
|
@SpringJUnitConfig(TestConfig.class)
|
||||||
|
class BeanOverrideTests {
|
||||||
|
|
||||||
@MockitoSpyBean("service") // <1>
|
@MockitoSpyBean("service") // <1>
|
||||||
CustomService customService;
|
CustomService customService;
|
||||||
|
|
||||||
// test case body...
|
// tests...
|
||||||
|
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
<1> Wrap the bean named `service` with a Mockito `spy`.
|
<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;
|
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}
|
* <p>Specifying this annotation registers the configured {@link BeanOverrideProcessor}
|
||||||
* which must be capable of handling the composed annotation and its attributes.
|
* 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
|
* <p>Since the composed annotation will typically only be applied to non-static
|
||||||
* expected that it is meta-annotated with {@link Target @Target(ElementType.FIELD)}.
|
* 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.convention.TestBean @TestBean},
|
||||||
* {@link org.springframework.test.context.bean.override.mockito.MockitoBean @MockitoBean}, and
|
* {@link org.springframework.test.context.bean.override.mockito.MockitoBean @MockitoBean}, and
|
||||||
* {@link org.springframework.test.context.bean.override.mockito.MockitoSpyBean @MockitoSpyBean}.
|
* {@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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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) {
|
Set<String> generatedBeanNames) {
|
||||||
|
|
||||||
String beanName = handler.getBeanName();
|
String beanName = handler.getBeanName();
|
||||||
Field field = handler.getField();
|
Assert.state(!BeanFactoryUtils.isFactoryDereference(beanName), () -> """
|
||||||
Assert.state(!BeanFactoryUtils.isFactoryDereference(beanName),() -> """
|
Unable to override bean '%s'%s: a FactoryBean cannot be overridden. \
|
||||||
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."""
|
||||||
To override the bean created by the FactoryBean, remove the '&' prefix.""".formatted(
|
.formatted(beanName, forField(handler.getField())));
|
||||||
beanName, field.getDeclaringClass().getSimpleName(), field.getName()));
|
|
||||||
|
|
||||||
switch (handler.getStrategy()) {
|
switch (handler.getStrategy()) {
|
||||||
case REPLACE -> replaceOrCreateBean(beanFactory, handler, generatedBeanNames, true);
|
case REPLACE -> replaceOrCreateBean(beanFactory, handler, generatedBeanNames, true);
|
||||||
|
@ -134,7 +133,6 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
|
||||||
// 4) Create bean by-name, with a provided name
|
// 4) Create bean by-name, with a provided name
|
||||||
|
|
||||||
String beanName = handler.getBeanName();
|
String beanName = handler.getBeanName();
|
||||||
Field field = handler.getField();
|
|
||||||
BeanDefinition existingBeanDefinition = null;
|
BeanDefinition existingBeanDefinition = null;
|
||||||
if (beanName == null) {
|
if (beanName == null) {
|
||||||
beanName = getBeanNameForType(beanFactory, handler, requireExistingBean);
|
beanName = getBeanNameForType(beanFactory, handler, requireExistingBean);
|
||||||
|
@ -169,11 +167,10 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
|
||||||
existingBeanDefinition = beanFactory.getBeanDefinition(beanName);
|
existingBeanDefinition = beanFactory.getBeanDefinition(beanName);
|
||||||
}
|
}
|
||||||
else if (requireExistingBean) {
|
else if (requireExistingBean) {
|
||||||
throw new IllegalStateException("""
|
Field field = handler.getField();
|
||||||
Unable to replace bean: there is no bean with name '%s' and type %s \
|
throw new IllegalStateException(
|
||||||
(as required by field '%s.%s')."""
|
"Unable to replace bean: there is no bean with name '%s' and type %s%s."
|
||||||
.formatted(beanName, handler.getBeanType(),
|
.formatted(beanName, handler.getBeanType(), requiredByField(field)));
|
||||||
field.getDeclaringClass().getSimpleName(), field.getName()));
|
|
||||||
}
|
}
|
||||||
// 4) We are creating a bean by-name with the provided beanName.
|
// 4) We are creating a bean by-name with the provided beanName.
|
||||||
}
|
}
|
||||||
|
@ -264,13 +261,11 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
|
||||||
else {
|
else {
|
||||||
String message = "Unable to select a bean to wrap: ";
|
String message = "Unable to select a bean to wrap: ";
|
||||||
if (candidateCount == 0) {
|
if (candidateCount == 0) {
|
||||||
message += "there are no beans of type %s (as required by field '%s.%s')."
|
message += "there are no beans of type %s%s.".formatted(beanType, requiredByField(field));
|
||||||
.formatted(beanType, field.getDeclaringClass().getSimpleName(), field.getName());
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
message += "found %d beans of type %s (as required by field '%s.%s'): %s"
|
message += "found %d beans of type %s%s: %s"
|
||||||
.formatted(candidateCount, beanType, field.getDeclaringClass().getSimpleName(),
|
.formatted(candidateCount, beanType, requiredByField(field), candidateNames);
|
||||||
field.getName(), candidateNames);
|
|
||||||
}
|
}
|
||||||
throw new IllegalStateException(message);
|
throw new IllegalStateException(message);
|
||||||
}
|
}
|
||||||
|
@ -281,11 +276,9 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
|
||||||
// We are wrapping an existing bean by-name.
|
// We are wrapping an existing bean by-name.
|
||||||
Set<String> candidates = getExistingBeanNamesByType(beanFactory, handler, false);
|
Set<String> candidates = getExistingBeanNamesByType(beanFactory, handler, false);
|
||||||
if (!candidates.contains(beanName)) {
|
if (!candidates.contains(beanName)) {
|
||||||
throw new IllegalStateException("""
|
throw new IllegalStateException(
|
||||||
Unable to wrap bean: there is no bean with name '%s' and type %s \
|
"Unable to wrap bean: there is no bean with name '%s' and type %s%s."
|
||||||
(as required by field '%s.%s')."""
|
.formatted(beanName, beanType, requiredByField(field)));
|
||||||
.formatted(beanName, beanType, field.getDeclaringClass().getSimpleName(),
|
|
||||||
field.getName()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,8 +301,8 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
|
||||||
else if (candidateCount == 0) {
|
else if (candidateCount == 0) {
|
||||||
if (requireExistingBean) {
|
if (requireExistingBean) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
"Unable to override bean: there are no beans of type %s (as required by field '%s.%s')."
|
"Unable to override bean: there are no beans of type %s%s."
|
||||||
.formatted(beanType, field.getDeclaringClass().getSimpleName(), field.getName()));
|
.formatted(beanType, requiredByField(field)));
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -320,14 +313,14 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
"Unable to select a bean to override: found %d beans of type %s (as required by field '%s.%s'): %s"
|
"Unable to select a bean to override: found %d beans of type %s%s: %s"
|
||||||
.formatted(candidateCount, beanType, field.getDeclaringClass().getSimpleName(),
|
.formatted(candidateCount, beanType, requiredByField(field), candidateNames));
|
||||||
field.getName(), candidateNames));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<String> getExistingBeanNamesByType(ConfigurableListableBeanFactory beanFactory, BeanOverrideHandler handler,
|
private Set<String> getExistingBeanNamesByType(ConfigurableListableBeanFactory beanFactory, BeanOverrideHandler handler,
|
||||||
boolean checkAutowiredCandidate) {
|
boolean checkAutowiredCandidate) {
|
||||||
|
|
||||||
|
Field field = handler.getField();
|
||||||
ResolvableType resolvableType = handler.getBeanType();
|
ResolvableType resolvableType = handler.getBeanType();
|
||||||
Class<?> type = resolvableType.toClass();
|
Class<?> type = resolvableType.toClass();
|
||||||
|
|
||||||
|
@ -345,16 +338,16 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter out non-matching autowire candidates.
|
// Filter out non-matching autowire candidates.
|
||||||
if (checkAutowiredCandidate) {
|
if (field != null && checkAutowiredCandidate) {
|
||||||
DependencyDescriptor descriptor = new DependencyDescriptor(handler.getField(), true);
|
DependencyDescriptor descriptor = new DependencyDescriptor(field, true);
|
||||||
beanNames.removeIf(beanName -> !beanFactory.isAutowireCandidate(beanName, descriptor));
|
beanNames.removeIf(beanName -> !beanFactory.isAutowireCandidate(beanName, descriptor));
|
||||||
}
|
}
|
||||||
// Filter out scoped proxy targets.
|
// Filter out scoped proxy targets.
|
||||||
beanNames.removeIf(ScopedProxyUtils::isScopedTarget);
|
beanNames.removeIf(ScopedProxyUtils::isScopedTarget);
|
||||||
|
|
||||||
// In case of multiple matches, fall back on the field's name as a last resort.
|
// In case of multiple matches, fall back on the field's name as a last resort.
|
||||||
if (beanNames.size() > 1) {
|
if (field != null && beanNames.size() > 1) {
|
||||||
String fieldName = handler.getField().getName();
|
String fieldName = field.getName();
|
||||||
if (beanNames.contains(fieldName)) {
|
if (beanNames.contains(fieldName)) {
|
||||||
return Set.of(fieldName);
|
return Set.of(fieldName);
|
||||||
}
|
}
|
||||||
|
@ -452,4 +445,19 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
|
||||||
dlbf.destroySingleton(beanName);
|
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.lang.Nullable;
|
||||||
import org.springframework.test.context.ContextConfigurationAttributes;
|
import org.springframework.test.context.ContextConfigurationAttributes;
|
||||||
import org.springframework.test.context.ContextCustomizerFactory;
|
import org.springframework.test.context.ContextCustomizerFactory;
|
||||||
import org.springframework.test.context.TestContextAnnotationUtils;
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -52,10 +51,7 @@ class BeanOverrideContextCustomizerFactory implements ContextCustomizerFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void findBeanOverrideHandlers(Class<?> testClass, Set<BeanOverrideHandler> handlers) {
|
private void findBeanOverrideHandlers(Class<?> testClass, Set<BeanOverrideHandler> handlers) {
|
||||||
if (TestContextAnnotationUtils.searchEnclosingClass(testClass)) {
|
BeanOverrideHandler.findAllHandlers(testClass).forEach(handler ->
|
||||||
findBeanOverrideHandlers(testClass.getEnclosingClass(), handlers);
|
|
||||||
}
|
|
||||||
BeanOverrideHandler.forTestClass(testClass).forEach(handler ->
|
|
||||||
Assert.state(handlers.add(handler), () ->
|
Assert.state(handlers.add(handler), () ->
|
||||||
"Duplicate BeanOverrideHandler discovered in test class %s: %s"
|
"Duplicate BeanOverrideHandler discovered in test class %s: %s"
|
||||||
.formatted(testClass.getName(), handler)));
|
.formatted(testClass.getName(), handler)));
|
||||||
|
|
|
@ -17,15 +17,18 @@
|
||||||
package org.springframework.test.context.bean.override;
|
package org.springframework.test.context.bean.override;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.AnnotatedElement;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.beans.factory.config.BeanDefinition;
|
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.annotation.MergedAnnotations;
|
||||||
import org.springframework.core.style.ToStringCreator;
|
import org.springframework.core.style.ToStringCreator;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.test.context.TestContextAnnotationUtils;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ReflectionUtils;
|
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
|
* <p>Concrete implementations of {@code BeanOverrideHandler} can store additional
|
||||||
* metadata to use during override {@linkplain #createOverrideInstance instance
|
* metadata to use during override {@linkplain #createOverrideInstance instance
|
||||||
* creation} — for example, based on further processing of the annotation
|
* creation} — for example, based on further processing of the annotation,
|
||||||
* or the annotated field.
|
* the annotated field, or the annotated class.
|
||||||
*
|
*
|
||||||
* <p><strong>NOTE</strong>: Only <em>singleton</em> beans can be overridden.
|
* <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.
|
* 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 {
|
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 Field field;
|
||||||
|
|
||||||
private final Set<Annotation> qualifierAnnotations;
|
private final Set<Annotation> qualifierAnnotations;
|
||||||
|
@ -81,7 +90,7 @@ public abstract class BeanOverrideHandler {
|
||||||
private final BeanOverrideStrategy strategy;
|
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) {
|
BeanOverrideStrategy strategy) {
|
||||||
|
|
||||||
this.field = field;
|
this.field = field;
|
||||||
|
@ -95,57 +104,116 @@ public abstract class BeanOverrideHandler {
|
||||||
* Process the given {@code testClass} and build the corresponding
|
* Process the given {@code testClass} and build the corresponding
|
||||||
* {@code BeanOverrideHandler} list derived from {@link BeanOverride @BeanOverride}
|
* {@code BeanOverrideHandler} list derived from {@link BeanOverride @BeanOverride}
|
||||||
* fields in the test class and its type hierarchy.
|
* 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
|
* @param testClass the test class to process
|
||||||
* @return a list of bean override handlers
|
* @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) {
|
public static List<BeanOverrideHandler> forTestClass(Class<?> testClass) {
|
||||||
List<BeanOverrideHandler> handlers = new LinkedList<>();
|
return findHandlers(testClass, true);
|
||||||
findHandlers(testClass, testClass, handlers);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
return handlers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find handlers using tail recursion to ensure that "locally declared"
|
* Find handlers using tail recursion to ensure that "locally declared" bean overrides
|
||||||
* bean overrides take precedence over inherited 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
|
* @since 6.2.2
|
||||||
*/
|
*/
|
||||||
private static void findHandlers(Class<?> clazz, Class<?> testClass, List<BeanOverrideHandler> handlers) {
|
private static void findHandlers(Class<?> clazz, Class<?> testClass, List<BeanOverrideHandler> handlers,
|
||||||
if (clazz == null || clazz == Object.class) {
|
boolean localFieldsOnly) {
|
||||||
return;
|
|
||||||
|
// 1) Search enclosing class hierarchy.
|
||||||
|
if (!localFieldsOnly && TestContextAnnotationUtils.searchEnclosingClass(clazz)) {
|
||||||
|
findHandlers(clazz.getEnclosingClass(), testClass, handlers, localFieldsOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1) Search type hierarchy.
|
// 2) Search class hierarchy.
|
||||||
findHandlers(clazz.getSuperclass(), testClass, handlers);
|
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));
|
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) {
|
private static void processField(Field field, Class<?> testClass, List<BeanOverrideHandler> handlers) {
|
||||||
AtomicBoolean overrideAnnotationFound = new AtomicBoolean();
|
AtomicBoolean overrideAnnotationFound = new AtomicBoolean();
|
||||||
MergedAnnotations.from(field, DIRECT).stream(BeanOverride.class).forEach(mergedAnnotation -> {
|
processElement(field, testClass, (processor, composedAnnotation) -> {
|
||||||
Assert.state(!Modifier.isStatic(field.getModifiers()),
|
Assert.state(!Modifier.isStatic(field.getModifiers()),
|
||||||
() -> "@BeanOverride field must not be static: " + field);
|
() -> "@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),
|
Assert.state(overrideAnnotationFound.compareAndSet(false, true),
|
||||||
() -> "Multiple @BeanOverride annotations found on field: " + field);
|
() -> "Multiple @BeanOverride annotations found on field: " + field);
|
||||||
BeanOverrideHandler handler = processor.createHandler(composedAnnotation, testClass, field);
|
handlers.add(processor.createHandler(composedAnnotation, testClass, field));
|
||||||
handlers.add(handler);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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}.
|
* Get the annotated {@link Field}.
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
public final Field getField() {
|
public final Field getField() {
|
||||||
return this.field;
|
return this.field;
|
||||||
}
|
}
|
||||||
|
@ -249,7 +317,10 @@ public abstract class BeanOverrideHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// by-type lookup
|
// 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));
|
this.qualifierAnnotations.equals(that.qualifierAnnotations));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,7 +328,7 @@ public abstract class BeanOverrideHandler {
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int hash = Objects.hash(getClass(), this.beanType.getType(), this.beanName, this.strategy);
|
int hash = Objects.hash(getClass(), this.beanType.getType(), this.beanName, this.strategy);
|
||||||
return (this.beanName != null ? hash : hash +
|
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
|
@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();
|
Annotation[] candidates = field.getDeclaredAnnotations();
|
||||||
if (candidates.length == 0) {
|
if (candidates.length == 0) {
|
||||||
return Collections.emptySet();
|
return Collections.emptySet();
|
||||||
|
|
|
@ -18,10 +18,13 @@ package org.springframework.test.context.bean.override;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Strategy interface for Bean Override processing, which creates a
|
* Strategy interface for Bean Override processing, which creates
|
||||||
* {@link BeanOverrideHandler} that drives how the target bean is overridden.
|
* {@link BeanOverrideHandler} instances that drive how target beans are
|
||||||
|
* overridden.
|
||||||
*
|
*
|
||||||
* <p>At least one composed annotation that is meta-annotated with
|
* <p>At least one composed annotation that is meta-annotated with
|
||||||
* {@link BeanOverride @BeanOverride} must be a companion of this processor and
|
* {@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.
|
* 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
|
* @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 testClass the test class to process
|
||||||
* @param field the annotated field
|
* @param field the annotated field
|
||||||
* @return the {@code BeanOverrideHandler} that should handle the given field
|
* @return the {@code BeanOverrideHandler} that should handle the given field
|
||||||
|
* @see #createHandlers(Annotation, Class)
|
||||||
*/
|
*/
|
||||||
BeanOverrideHandler createHandler(Annotation overrideAnnotation, Class<?> testClass, Field field);
|
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) {
|
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);
|
String beanName = this.handlerToBeanNameMap.get(handler);
|
||||||
Assert.state(StringUtils.hasLength(beanName), () -> "No bean found for BeanOverrideHandler: " + 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) {
|
private void inject(Field field, Object target, String beanName) {
|
||||||
|
|
|
@ -38,7 +38,7 @@ abstract class AbstractMockitoBeanOverrideHandler extends BeanOverrideHandler {
|
||||||
private final MockReset reset;
|
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) {
|
@Nullable String beanName, BeanOverrideStrategy strategy, @Nullable MockReset reset) {
|
||||||
|
|
||||||
super(field, beanType, beanName, strategy);
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.Documented;
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Repeatable;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
@ -29,17 +30,37 @@ import org.springframework.core.annotation.AliasFor;
|
||||||
import org.springframework.test.context.bean.override.BeanOverride;
|
import org.springframework.test.context.bean.override.BeanOverride;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code @MockitoBean} is an annotation that can be applied to a non-static field
|
* {@code @MockitoBean} is an annotation that can be used in test classes to
|
||||||
* in a test class to override a bean in the test's
|
* override beans in a test's
|
||||||
* {@link org.springframework.context.ApplicationContext ApplicationContext}
|
* {@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
|
* <p>{@code @MockitoBean} can be applied in the following ways.
|
||||||
* field. If multiple candidates exist, a {@code @Qualifier} annotation can be
|
* <ul>
|
||||||
* used to help disambiguate. In the absence of a {@code @Qualifier} annotation,
|
* <li>On a non-static field in a test class or any of its superclasses.</li>
|
||||||
* the name of the annotated field will be used as a fallback qualifier.
|
* <li>On a non-static field in an enclosing class for a {@code @Nested} test class
|
||||||
* Alternatively, you can explicitly specify a bean name to mock by setting the
|
* or in any class in the type hierarchy or enclosing class hierarchy above the
|
||||||
* {@link #value() value} or {@link #name() name} attribute.
|
* {@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
|
* <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,
|
* 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
|
* (default visibility), or {@code private} depending on the needs or coding
|
||||||
* practices of the project.
|
* practices of the project.
|
||||||
*
|
*
|
||||||
* <p>{@code @MockitoBean} fields will be inherited from an enclosing test class by default.
|
* <p>{@code @MockitoBean} fields and type-level {@code @MockitoBean} declarations
|
||||||
* See {@link org.springframework.test.context.NestedTestConfiguration @NestedTestConfiguration}
|
* will be inherited from an enclosing test class by default. See
|
||||||
|
* {@link org.springframework.test.context.NestedTestConfiguration @NestedTestConfiguration}
|
||||||
* for details.
|
* 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 Simon Baslé
|
||||||
* @author Sam Brannen
|
* @author Sam Brannen
|
||||||
* @since 6.2
|
* @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.mockito.MockitoSpyBean @MockitoSpyBean
|
||||||
* @see org.springframework.test.context.bean.override.convention.TestBean @TestBean
|
* @see org.springframework.test.context.bean.override.convention.TestBean @TestBean
|
||||||
*/
|
*/
|
||||||
@Target(ElementType.FIELD)
|
@Target({ElementType.FIELD, ElementType.TYPE})
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Documented
|
@Documented
|
||||||
|
@Repeatable(MockitoBeans.class)
|
||||||
@BeanOverride(MockitoBeanOverrideProcessor.class)
|
@BeanOverride(MockitoBeanOverrideProcessor.class)
|
||||||
public @interface MockitoBean {
|
public @interface MockitoBean {
|
||||||
|
|
||||||
|
@ -91,13 +122,27 @@ public @interface MockitoBean {
|
||||||
/**
|
/**
|
||||||
* Name of the bean to mock.
|
* Name of the bean to mock.
|
||||||
* <p>If left unspecified, the bean to mock is selected according to the
|
* <p>If left unspecified, the bean to mock is selected according to the
|
||||||
* annotated field's type, taking qualifiers into account if necessary. See
|
* configured {@link #types() types} or the annotated field's type, taking
|
||||||
* the {@linkplain MockitoBean class-level documentation} for details.
|
* qualifiers into account if necessary. See the {@linkplain MockitoBean
|
||||||
|
* class-level documentation} for details.
|
||||||
* @see #value()
|
* @see #value()
|
||||||
*/
|
*/
|
||||||
@AliasFor("value")
|
@AliasFor("value")
|
||||||
String name() default "";
|
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.
|
* Extra interfaces that should also be declared by the mock.
|
||||||
* <p>Defaults to none.
|
* <p>Defaults to none.
|
||||||
|
|
|
@ -57,13 +57,17 @@ class MockitoBeanOverrideHandler extends AbstractMockitoBeanOverrideHandler {
|
||||||
private final boolean serializable;
|
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),
|
this(field, typeToMock, (!mockitoBean.name().isBlank() ? mockitoBean.name() : null),
|
||||||
(mockitoBean.enforceOverride() ? REPLACE : REPLACE_OR_CREATE),
|
(mockitoBean.enforceOverride() ? REPLACE : REPLACE_OR_CREATE),
|
||||||
mockitoBean.reset(), mockitoBean.extraInterfaces(), mockitoBean.answers(), mockitoBean.serializable());
|
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,
|
BeanOverrideStrategy strategy, MockReset reset, Class<?>[] extraInterfaces, Answers answers,
|
||||||
boolean serializable) {
|
boolean serializable) {
|
||||||
|
|
||||||
|
|
|
@ -18,15 +18,20 @@ package org.springframework.test.context.bean.override.mockito;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.core.ResolvableType;
|
import org.springframework.core.ResolvableType;
|
||||||
|
import org.springframework.test.context.bean.override.BeanOverrideHandler;
|
||||||
import org.springframework.test.context.bean.override.BeanOverrideProcessor;
|
import org.springframework.test.context.bean.override.BeanOverrideProcessor;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link BeanOverrideProcessor} implementation that provides support for
|
* {@link BeanOverrideProcessor} implementation that provides support for
|
||||||
* {@link MockitoBean @MockitoBean} and {@link MockitoSpyBean @MockitoSpyBean}.
|
* {@link MockitoBean @MockitoBean} and {@link MockitoSpyBean @MockitoSpyBean}.
|
||||||
*
|
*
|
||||||
* @author Simon Baslé
|
* @author Simon Baslé
|
||||||
|
* @author Sam Brannen
|
||||||
* @since 6.2
|
* @since 6.2
|
||||||
* @see MockitoBean @MockitoBean
|
* @see MockitoBean @MockitoBean
|
||||||
* @see MockitoSpyBean @MockitoSpyBean
|
* @see MockitoSpyBean @MockitoSpyBean
|
||||||
|
@ -36,6 +41,8 @@ class MockitoBeanOverrideProcessor implements BeanOverrideProcessor {
|
||||||
@Override
|
@Override
|
||||||
public AbstractMockitoBeanOverrideHandler createHandler(Annotation overrideAnnotation, Class<?> testClass, Field field) {
|
public AbstractMockitoBeanOverrideHandler createHandler(Annotation overrideAnnotation, Class<?> testClass, Field field) {
|
||||||
if (overrideAnnotation instanceof MockitoBean mockitoBean) {
|
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);
|
return new MockitoBeanOverrideHandler(field, ResolvableType.forField(field, testClass), mockitoBean);
|
||||||
}
|
}
|
||||||
else if (overrideAnnotation instanceof MockitoSpyBean spyBean) {
|
else if (overrideAnnotation instanceof MockitoSpyBean spyBean) {
|
||||||
|
@ -47,4 +54,23 @@ class MockitoBeanOverrideProcessor implements BeanOverrideProcessor {
|
||||||
.formatted(field.getDeclaringClass().getName(), field.getName()));
|
.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);
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -67,6 +67,76 @@ class MockitoBeanOverrideHandlerTests {
|
||||||
assertThat(handler1).hasSameHashCodeAs(handler2);
|
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
|
@Test
|
||||||
void isNotEqualEqualToByTypeLookupWithSameMetadataButDifferentField() {
|
void isNotEqualEqualToByTypeLookupWithSameMetadataButDifferentField() {
|
||||||
MockitoBeanOverrideHandler handler1 = createHandler(sampleField("service"));
|
MockitoBeanOverrideHandler handler1 = createHandler(sampleField("service"));
|
||||||
|
@ -122,6 +192,11 @@ class MockitoBeanOverrideHandlerTests {
|
||||||
return new MockitoBeanOverrideHandler(field, ResolvableType.forClass(field.getType()), annotation);
|
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 {
|
static class SampleOneMock {
|
||||||
|
|
||||||
|
@ -159,4 +234,28 @@ class MockitoBeanOverrideHandlerTests {
|
||||||
private String service7;
|
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.annotation.Annotation;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.core.annotation.AnnotationUtils;
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
|
@ -41,43 +43,155 @@ class MockitoBeanOverrideProcessorTests {
|
||||||
private final MockitoBeanOverrideProcessor processor = new MockitoBeanOverrideProcessor();
|
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
|
@Test
|
||||||
void mockAnnotationCreatesMockitoBeanOverrideHandler() {
|
void mockAnnotationCreatesMockitoBeanOverrideHandler() {
|
||||||
MockitoBean annotation = AnnotationUtils.synthesizeAnnotation(MockitoBean.class);
|
MockitoBean annotation = AnnotationUtils.synthesizeAnnotation(MockitoBean.class);
|
||||||
BeanOverrideHandler object = processor.createHandler(annotation, TestCase.class, field);
|
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
|
@Nested
|
||||||
void spyAnnotationCreatesMockitoSpyBeanOverrideHandler() {
|
class CreateHandlersTests {
|
||||||
MockitoSpyBean annotation = AnnotationUtils.synthesizeAnnotation(MockitoSpyBean.class);
|
|
||||||
BeanOverrideHandler object = processor.createHandler(annotation, TestCase.class, field);
|
|
||||||
|
|
||||||
assertThat(object).isExactlyInstanceOf(MockitoSpyBeanOverrideHandler.class);
|
@Test
|
||||||
}
|
void missingTypes() {
|
||||||
|
Class<?> testClass = MissingTypesTestCase.class;
|
||||||
|
MockitoBean annotation = testClass.getAnnotation(MockitoBean.class);
|
||||||
|
|
||||||
@Test
|
assertThatIllegalStateException()
|
||||||
void otherAnnotationThrows() {
|
.isThrownBy(() -> processor.createHandlers(annotation, testClass))
|
||||||
Annotation annotation = field.getAnnotation(Nullable.class);
|
.withMessage("The @MockitoBean 'types' attribute must not be empty when declared on a class");
|
||||||
|
}
|
||||||
|
|
||||||
assertThatIllegalStateException()
|
@Test
|
||||||
.isThrownBy(() -> processor.createHandler(annotation, TestCase.class, field))
|
void nameNotSupportedWithMultipleTypes() {
|
||||||
.withMessage("Invalid annotation passed to MockitoBeanOverrideProcessor: expected either " +
|
Class<?> testClass = NameNotSupportedWithMultipleTypesTestCase.class;
|
||||||
"@MockitoBean or @MockitoSpyBean on field %s.%s", field.getDeclaringClass().getName(),
|
MockitoBean annotation = testClass.getAnnotation(MockitoBean.class);
|
||||||
field.getName());
|
|
||||||
}
|
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
|
@MockitoBean
|
||||||
@MockitoSpyBean
|
static class MissingTypesTestCase {
|
||||||
Integer number;
|
}
|
||||||
|
|
||||||
|
@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