Merge branch '6.2.x'
This commit is contained in:
commit
8a53525209
|
@ -1,34 +1,60 @@
|
||||||
[[spring-testing-annotation-beanoverriding-mockitobean]]
|
[[spring-testing-annotation-beanoverriding-mockitobean]]
|
||||||
= `@MockitoBean` and `@MockitoSpyBean`
|
= `@MockitoBean` and `@MockitoSpyBean`
|
||||||
|
|
||||||
`@MockitoBean` and `@MockitoSpyBean` are used on non-static fields in test classes to
|
`@MockitoBean` and `@MockitoSpyBean` can be used in test classes to override a bean in
|
||||||
override beans in the test's `ApplicationContext` with a Mockito _mock_ or _spy_,
|
the test's `ApplicationContext` with a Mockito _mock_ or _spy_, respectively. In the
|
||||||
respectively. In the latter case, an early instance of the original bean is captured and
|
latter case, an early instance of the original bean is captured and wrapped by the spy.
|
||||||
wrapped by the spy.
|
|
||||||
|
|
||||||
By default, the annotated field's type is used to search for candidate beans to override.
|
The annotations can be applied in the following ways.
|
||||||
If multiple candidates match, `@Qualifier` can be provided to narrow the candidate to
|
|
||||||
override. Alternatively, a candidate whose bean name matches the name of the field will
|
* On a non-static field in a test class or any of its superclasses.
|
||||||
match.
|
* On a non-static field in an enclosing class for a `@Nested` test class or in any class
|
||||||
|
in the type hierarchy or enclosing class hierarchy above the `@Nested` test class.
|
||||||
|
* At the type level on a test class or any superclass or implemented interface in the
|
||||||
|
type hierarchy above the test class.
|
||||||
|
* At the type level on an enclosing class for a `@Nested` test class or on any class or
|
||||||
|
interface in the type hierarchy or enclosing class hierarchy above the `@Nested` test
|
||||||
|
class.
|
||||||
|
|
||||||
|
When `@MockitoBean` or `@MockitoSpyBean` is declared on a field, the bean to mock or spy
|
||||||
|
is inferred from the type of the annotated field. If multiple candidates exist in the
|
||||||
|
`ApplicationContext`, a `@Qualifier` annotation can be declared on the field to help
|
||||||
|
disambiguate. In the absence of a `@Qualifier` annotation, the name of the annotated
|
||||||
|
field will be used as a _fallback qualifier_. Alternatively, you can explicitly specify a
|
||||||
|
bean name to mock or spy by setting the `value` or `name` attribute in the annotation.
|
||||||
|
|
||||||
|
When `@MockitoBean` or `@MockitoSpyBean` is declared at the type level, the type of bean
|
||||||
|
(or beans) to mock or spy must be supplied via the `types` attribute in the annotation –
|
||||||
|
for example, `@MockitoBean(types = {OrderService.class, UserService.class})`. If multiple
|
||||||
|
candidates exist in the `ApplicationContext`, you can explicitly specify a bean name to
|
||||||
|
mock or spy by setting the `name` attribute. Note, however, that the `types` attribute
|
||||||
|
must contain a single type if an explicit bean `name` is configured – for example,
|
||||||
|
`@MockitoBean(name = "ps1", types = PrintingService.class)`.
|
||||||
|
|
||||||
|
To support reuse of mock configuration, `@MockitoBean` and `@MockitoSpyBean` may be used
|
||||||
|
as meta-annotations to create custom _composed annotations_ – for example, to define
|
||||||
|
common mock or spy configuration in a single annotation that can be reused across a test
|
||||||
|
suite. `@MockitoBean` and `@MockitoSpyBean` can also be used as repeatable annotations at
|
||||||
|
the type level — for example, to mock or spy several beans by name.
|
||||||
|
|
||||||
[WARNING]
|
[WARNING]
|
||||||
====
|
====
|
||||||
Qualifiers, including the name of the field, are used to determine if a separate
|
Qualifiers, including the name of a field, are used to determine if a separate
|
||||||
`ApplicationContext` needs to be created. If you are using this feature to mock or spy
|
`ApplicationContext` needs to be created. If you are using this feature to mock or spy
|
||||||
the same bean in several test classes, make sure to name the field consistently to avoid
|
the same bean in several test classes, make sure to name the fields consistently to avoid
|
||||||
creating unnecessary contexts.
|
creating unnecessary contexts.
|
||||||
====
|
====
|
||||||
|
|
||||||
Each annotation also defines Mockito-specific attributes to fine-tune the mocking behavior.
|
Each annotation also defines Mockito-specific attributes to fine-tune the mocking behavior.
|
||||||
|
|
||||||
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-strategy[strategy for bean overrides].
|
||||||
If no existing bean matches, a new bean is created on the fly. However, you can switch to
|
If a corresponding bean does not exist, a new bean will be created. However, you can
|
||||||
the `REPLACE` strategy by setting the `enforceOverride` attribute to `true`. See the
|
switch to the `REPLACE` strategy by setting the `enforceOverride` attribute to `true` –
|
||||||
following section for an example.
|
for example, `@MockitoBean(enforceOverride = true)`.
|
||||||
|
|
||||||
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-strategy[strategy],
|
||||||
and the original instance is wrapped in a Mockito spy. This strategy requires that
|
and the original instance is wrapped in a Mockito spy. This strategy requires that
|
||||||
exactly one candidate bean exists.
|
exactly one candidate bean exists.
|
||||||
|
|
||||||
|
@ -56,15 +82,8 @@ or `private` depending on the needs or coding practices of the project.
|
||||||
[[spring-testing-annotation-beanoverriding-mockitobean-examples]]
|
[[spring-testing-annotation-beanoverriding-mockitobean-examples]]
|
||||||
== `@MockitoBean` Examples
|
== `@MockitoBean` Examples
|
||||||
|
|
||||||
When using `@MockitoBean`, a new bean will be created if a corresponding bean does not
|
The following example shows how to use the default behavior of the `@MockitoBean`
|
||||||
exist. However, if you would like for the test to fail when a corresponding bean does not
|
annotation.
|
||||||
exist, you can set the `enforceOverride` attribute to `true` – for example,
|
|
||||||
`@MockitoBean(enforceOverride = true)`.
|
|
||||||
|
|
||||||
To use a by-name override rather than a by-type override, specify the `name` (or `value`)
|
|
||||||
attribute of the annotation.
|
|
||||||
|
|
||||||
The following example shows how to use the default behavior of the `@MockitoBean` annotation:
|
|
||||||
|
|
||||||
[tabs]
|
[tabs]
|
||||||
======
|
======
|
||||||
|
@ -81,7 +100,7 @@ Java::
|
||||||
// tests...
|
// tests...
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
<1> Replace the bean with type `CustomService` with a Mockito `mock`.
|
<1> Replace the bean with type `CustomService` with a Mockito mock.
|
||||||
======
|
======
|
||||||
|
|
||||||
In the example above, we are creating a mock for `CustomService`. If more than one bean
|
In the example above, we are creating a mock for `CustomService`. If more than one bean
|
||||||
|
@ -90,7 +109,8 @@ will fail, and you will need to provide a qualifier of some sort to identify whi
|
||||||
`CustomService` beans you want to override. If no such bean exists, a bean will be
|
`CustomService` beans you want to override. If no such bean exists, a bean 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. If no bean
|
||||||
|
named `service` exists, one is created.
|
||||||
|
|
||||||
[tabs]
|
[tabs]
|
||||||
======
|
======
|
||||||
|
@ -108,32 +128,9 @@ Java::
|
||||||
|
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
<1> Replace the bean named `service` with a Mockito `mock`.
|
<1> Replace the bean named `service` with a Mockito mock.
|
||||||
======
|
======
|
||||||
|
|
||||||
If no bean named `service` exists, one is created.
|
|
||||||
|
|
||||||
`@MockitoBean` can also be used at the type level:
|
|
||||||
|
|
||||||
- on a test class or any superclass or implemented interface in the type hierarchy above
|
|
||||||
the test class
|
|
||||||
- on an enclosing class for a `@Nested` test class or on any class or interface in the
|
|
||||||
type hierarchy or enclosing class hierarchy above the `@Nested` test class
|
|
||||||
|
|
||||||
When `@MockitoBean` is declared at the type level, the type of bean (or beans) to mock
|
|
||||||
must be supplied via the `types` attribute – for example,
|
|
||||||
`@MockitoBean(types = {OrderService.class, UserService.class})`. If multiple candidates
|
|
||||||
exist in the application context, you can explicitly specify a bean name to mock by
|
|
||||||
setting the `name` attribute. Note, however, that the `types` attribute must contain a
|
|
||||||
single type if an explicit bean `name` is configured – for example,
|
|
||||||
`@MockitoBean(name = "ps1", types = PrintingService.class)`.
|
|
||||||
|
|
||||||
To support reuse of mock configuration, `@MockitoBean` may be used as a meta-annotation
|
|
||||||
to create custom _composed annotations_ — for example, to define common mock
|
|
||||||
configuration in a single annotation that can be reused across a test suite.
|
|
||||||
`@MockitoBean` can also be used as a repeatable annotation at the type level — for
|
|
||||||
example, to mock several beans by name.
|
|
||||||
|
|
||||||
The following `@SharedMocks` annotation registers two mocks by-type and one mock by-name.
|
The following `@SharedMocks` annotation registers two mocks by-type and one mock by-name.
|
||||||
|
|
||||||
[tabs]
|
[tabs]
|
||||||
|
@ -191,7 +188,7 @@ APIs.
|
||||||
== `@MockitoSpyBean` Examples
|
== `@MockitoSpyBean` Examples
|
||||||
|
|
||||||
The following example shows how to use the default behavior of the `@MockitoSpyBean`
|
The following example shows how to use the default behavior of the `@MockitoSpyBean`
|
||||||
annotation:
|
annotation.
|
||||||
|
|
||||||
[tabs]
|
[tabs]
|
||||||
======
|
======
|
||||||
|
@ -208,7 +205,7 @@ Java::
|
||||||
// tests...
|
// tests...
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
<1> Wrap the bean with type `CustomService` with a Mockito `spy`.
|
<1> Wrap the bean with type `CustomService` with a Mockito spy.
|
||||||
======
|
======
|
||||||
|
|
||||||
In the example above, we are wrapping the bean with type `CustomService`. If more than
|
In the example above, we are wrapping the bean with type `CustomService`. If more than
|
||||||
|
@ -216,7 +213,7 @@ one bean of that type exists, the bean named `customService` is considered. Othe
|
||||||
the test will fail, and you will need to provide a qualifier of some sort to identify
|
the test will fail, and you will need to provide a qualifier of some sort to identify
|
||||||
which of the `CustomService` beans you want to spy.
|
which of the `CustomService` beans you want to spy.
|
||||||
|
|
||||||
The following example uses a by-name lookup, rather than a by-type lookup:
|
The following example uses a by-name lookup, rather than a by-type lookup.
|
||||||
|
|
||||||
[tabs]
|
[tabs]
|
||||||
======
|
======
|
||||||
|
@ -233,5 +230,58 @@ Java::
|
||||||
// tests...
|
// tests...
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
<1> Wrap the bean named `service` with a Mockito `spy`.
|
<1> Wrap the bean named `service` with a Mockito spy.
|
||||||
======
|
======
|
||||||
|
|
||||||
|
The following `@SharedSpies` annotation registers two spies by-type and one spy by-name.
|
||||||
|
|
||||||
|
[tabs]
|
||||||
|
======
|
||||||
|
Java::
|
||||||
|
+
|
||||||
|
[source,java,indent=0,subs="verbatim,quotes"]
|
||||||
|
----
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@MockitoSpyBean(types = {OrderService.class, UserService.class}) // <1>
|
||||||
|
@MockitoSpyBean(name = "ps1", types = PrintingService.class) // <2>
|
||||||
|
public @interface SharedSpies {
|
||||||
|
}
|
||||||
|
----
|
||||||
|
<1> Register `OrderService` and `UserService` spies by-type.
|
||||||
|
<2> Register `PrintingService` spy by-name.
|
||||||
|
======
|
||||||
|
|
||||||
|
The following demonstrates how `@SharedSpies` can be used on a test class.
|
||||||
|
|
||||||
|
[tabs]
|
||||||
|
======
|
||||||
|
Java::
|
||||||
|
+
|
||||||
|
[source,java,indent=0,subs="verbatim,quotes"]
|
||||||
|
----
|
||||||
|
@SpringJUnitConfig(TestConfig.class)
|
||||||
|
@SharedSpies // <1>
|
||||||
|
class BeanOverrideTests {
|
||||||
|
|
||||||
|
@Autowired OrderService orderService; // <2>
|
||||||
|
|
||||||
|
@Autowired UserService userService; // <2>
|
||||||
|
|
||||||
|
@Autowired PrintingService ps1; // <2>
|
||||||
|
|
||||||
|
// Inject other components that rely on the spies.
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testThatDependsOnMocks() {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
<1> Register common spies via the custom `@SharedSpies` annotation.
|
||||||
|
<2> Optionally inject spies to _stub_ or _verify_ them.
|
||||||
|
======
|
||||||
|
|
||||||
|
TIP: The spies can also be injected into `@Configuration` classes or other test-related
|
||||||
|
components in the `ApplicationContext` in order to configure them with Mockito's stubbing
|
||||||
|
APIs.
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
= Bean Overriding in Tests
|
= Bean Overriding in Tests
|
||||||
|
|
||||||
Bean overriding in tests refers to the ability to override specific beans in the
|
Bean overriding in tests refers to the ability to override specific beans in the
|
||||||
`ApplicationContext` for a test class, by annotating one or more non-static fields in the
|
`ApplicationContext` for a test class, by annotating the test class or one or more
|
||||||
test class.
|
non-static fields in the test class.
|
||||||
|
|
||||||
NOTE: This feature is intended as a less risky alternative to the practice of registering
|
NOTE: This feature is intended as a less risky alternative to the practice of registering
|
||||||
a bean via `@Bean` with the `DefaultListableBeanFactory`
|
a bean via `@Bean` with the `DefaultListableBeanFactory`
|
||||||
|
@ -42,15 +42,16 @@ The `spring-test` module registers implementations of the latter two
|
||||||
{spring-framework-code}/spring-test/src/main/resources/META-INF/spring.factories[`META-INF/spring.factories`
|
{spring-framework-code}/spring-test/src/main/resources/META-INF/spring.factories[`META-INF/spring.factories`
|
||||||
properties file].
|
properties file].
|
||||||
|
|
||||||
The bean overriding infrastructure searches in test classes for any non-static field that
|
The bean overriding infrastructure searches for annotations on test classes as well as
|
||||||
is meta-annotated with `@BeanOverride` and instantiates the corresponding
|
annotations on non-static fields in test classes that are meta-annotated with
|
||||||
`BeanOverrideProcessor` which is responsible for creating an appropriate
|
`@BeanOverride` and instantiates the corresponding `BeanOverrideProcessor` which is
|
||||||
`BeanOverrideHandler`.
|
responsible for creating an appropriate `BeanOverrideHandler`.
|
||||||
|
|
||||||
The internal `BeanOverrideBeanFactoryPostProcessor` then uses bean override handlers to
|
The internal `BeanOverrideBeanFactoryPostProcessor` then uses bean override handlers to
|
||||||
alter the test's `ApplicationContext` by creating, replacing, or wrapping beans as
|
alter the test's `ApplicationContext` by creating, replacing, or wrapping beans as
|
||||||
defined by the corresponding `BeanOverrideStrategy`:
|
defined by the corresponding `BeanOverrideStrategy`:
|
||||||
|
|
||||||
|
[[testcontext-bean-overriding-strategy]]
|
||||||
`REPLACE`::
|
`REPLACE`::
|
||||||
Replaces the bean. Throws an exception if a corresponding bean does not exist.
|
Replaces the bean. Throws an exception if a corresponding bean does not exist.
|
||||||
`REPLACE_OR_CREATE`::
|
`REPLACE_OR_CREATE`::
|
||||||
|
|
|
@ -31,9 +31,9 @@ import org.springframework.test.context.bean.override.BeanOverride;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code @MockitoBean} is an annotation that can be used in test classes to
|
* {@code @MockitoBean} is an annotation that can be used in test classes to
|
||||||
* override beans in a test's
|
* override a bean in the test's
|
||||||
* {@link org.springframework.context.ApplicationContext ApplicationContext}
|
* {@link org.springframework.context.ApplicationContext ApplicationContext}
|
||||||
* using Mockito mocks.
|
* with a Mockito mock.
|
||||||
*
|
*
|
||||||
* <p>{@code @MockitoBean} can be applied in the following ways.
|
* <p>{@code @MockitoBean} can be applied in the following ways.
|
||||||
* <ul>
|
* <ul>
|
||||||
|
@ -49,18 +49,19 @@ import org.springframework.test.context.bean.override.BeanOverride;
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>When {@code @MockitoBean} is declared on a field, the bean to mock is inferred
|
* <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
|
* from the type of the annotated field. If multiple candidates exist in the
|
||||||
* {@code @Qualifier} annotation can be declared on the field to help disambiguate.
|
* {@code ApplicationContext}, a {@code @Qualifier} annotation can be declared
|
||||||
* In the absence of a {@code @Qualifier} annotation, the name of the annotated
|
* on the field to help disambiguate. In the absence of a {@code @Qualifier}
|
||||||
* field will be used as a fallback qualifier. Alternatively, you can explicitly
|
* annotation, the name of the annotated field will be used as a <em>fallback
|
||||||
* specify a bean name to mock by setting the {@link #value() value} or
|
* qualifier</em>. Alternatively, you can explicitly specify a bean name to mock
|
||||||
* {@link #name() name} attribute.
|
* 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
|
* <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
|
* (or beans) to mock must be supplied via the {@link #types() types} attribute.
|
||||||
* candidates exist, you can explicitly specify a bean name to mock by setting the
|
* If multiple candidates exist in the {@code ApplicationContext}, you can
|
||||||
* {@link #name() name} attribute. Note, however, that the {@code types} attribute
|
* explicitly specify a bean name to mock by setting the {@link #name() name}
|
||||||
* must contain a single type if an explicit bean {@code name} is configured.
|
* 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,
|
||||||
|
@ -111,7 +112,7 @@ import org.springframework.test.context.bean.override.BeanOverride;
|
||||||
public @interface MockitoBean {
|
public @interface MockitoBean {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alias for {@link #name()}.
|
* Alias for {@link #name() name}.
|
||||||
* <p>Intended to be used when no other attributes are needed — for
|
* <p>Intended to be used when no other attributes are needed — for
|
||||||
* example, {@code @MockitoBean("customBeanName")}.
|
* example, {@code @MockitoBean("customBeanName")}.
|
||||||
* @see #name()
|
* @see #name()
|
||||||
|
@ -136,7 +137,7 @@ public @interface MockitoBean {
|
||||||
* <p>Each type specified will result in a mock being created and registered
|
* <p>Each type specified will result in a mock being created and registered
|
||||||
* with the {@code ApplicationContext}.
|
* with the {@code ApplicationContext}.
|
||||||
* <p>Types must be omitted when the annotation is used on a field.
|
* <p>Types must be omitted when the annotation is used on a field.
|
||||||
* <p>When {@code @MockitoBean} also defines a {@link #name}, this attribute
|
* <p>When {@code @MockitoBean} also defines a {@link #name name}, this attribute
|
||||||
* can only contain a single value.
|
* can only contain a single value.
|
||||||
* @return the types to mock
|
* @return the types to mock
|
||||||
* @since 6.2.2
|
* @since 6.2.2
|
||||||
|
|
|
@ -45,8 +45,10 @@ class MockitoBeanOverrideProcessor implements BeanOverrideProcessor {
|
||||||
"The @MockitoBean 'types' attribute must be omitted when declared on a field");
|
"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 mockitoSpyBean) {
|
||||||
return new MockitoSpyBeanOverrideHandler(field, ResolvableType.forField(field, testClass), spyBean);
|
Assert.state(mockitoSpyBean.types().length == 0,
|
||||||
|
"The @MockitoSpyBean 'types' attribute must be omitted when declared on a field");
|
||||||
|
return new MockitoSpyBeanOverrideHandler(field, ResolvableType.forField(field, testClass), mockitoSpyBean);
|
||||||
}
|
}
|
||||||
throw new IllegalStateException("""
|
throw new IllegalStateException("""
|
||||||
Invalid annotation passed to MockitoBeanOverrideProcessor: \
|
Invalid annotation passed to MockitoBeanOverrideProcessor: \
|
||||||
|
@ -56,11 +58,7 @@ class MockitoBeanOverrideProcessor implements BeanOverrideProcessor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<BeanOverrideHandler> createHandlers(Annotation overrideAnnotation, Class<?> testClass) {
|
public List<BeanOverrideHandler> createHandlers(Annotation overrideAnnotation, Class<?> testClass) {
|
||||||
if (!(overrideAnnotation instanceof MockitoBean mockitoBean)) {
|
if (overrideAnnotation instanceof MockitoBean mockitoBean) {
|
||||||
throw new IllegalStateException("""
|
|
||||||
Invalid annotation passed to MockitoBeanOverrideProcessor: \
|
|
||||||
expected @MockitoBean on test class """ + testClass.getName());
|
|
||||||
}
|
|
||||||
Class<?>[] types = mockitoBean.types();
|
Class<?>[] types = mockitoBean.types();
|
||||||
Assert.state(types.length > 0,
|
Assert.state(types.length > 0,
|
||||||
"The @MockitoBean 'types' attribute must not be empty when declared on a class");
|
"The @MockitoBean 'types' attribute must not be empty when declared on a class");
|
||||||
|
@ -72,5 +70,22 @@ class MockitoBeanOverrideProcessor implements BeanOverrideProcessor {
|
||||||
}
|
}
|
||||||
return handlers;
|
return handlers;
|
||||||
}
|
}
|
||||||
|
else if (overrideAnnotation instanceof MockitoSpyBean mockitoSpyBean) {
|
||||||
|
Class<?>[] types = mockitoSpyBean.types();
|
||||||
|
Assert.state(types.length > 0,
|
||||||
|
"The @MockitoSpyBean 'types' attribute must not be empty when declared on a class");
|
||||||
|
Assert.state(mockitoSpyBean.name().isEmpty() || types.length == 1,
|
||||||
|
"The @MockitoSpyBean 'name' attribute cannot be used when mocking multiple types");
|
||||||
|
List<BeanOverrideHandler> handlers = new ArrayList<>();
|
||||||
|
for (Class<?> type : types) {
|
||||||
|
handlers.add(new MockitoSpyBeanOverrideHandler(ResolvableType.forClass(type), mockitoSpyBean));
|
||||||
|
}
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("""
|
||||||
|
Invalid annotation passed to MockitoBeanOverrideProcessor: \
|
||||||
|
expected either @MockitoBean or @MockitoSpyBean on test class %s"""
|
||||||
|
.formatted(testClass.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -26,19 +27,40 @@ import org.springframework.core.annotation.AliasFor;
|
||||||
import org.springframework.test.context.bean.override.BeanOverride;
|
import org.springframework.test.context.bean.override.BeanOverride;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code @MockitoSpyBean} is an annotation that can be applied to a non-static
|
* {@code @MockitoSpyBean} is an annotation that can be used in test classes to
|
||||||
* field in a test class to override a bean in the test's
|
* override a bean in the test's
|
||||||
* {@link org.springframework.context.ApplicationContext ApplicationContext}
|
* {@link org.springframework.context.ApplicationContext ApplicationContext}
|
||||||
* with a Mockito spy that wraps the original bean instance.
|
* with a Mockito spy that wraps the original bean instance.
|
||||||
*
|
*
|
||||||
* <p>By default, the bean to spy is inferred from the type of the annotated
|
* <p>{@code @MockitoSpyBean} 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 spy 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. If a bean name is
|
* {@code @Nested} test class.</li>
|
||||||
* specified, it is required that a target bean with that name has been previously
|
* <li>At the type level on a test class or any superclass or implemented interface
|
||||||
* registered in the application context.
|
* 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 @MockitoSpyBean} is declared on a field, the bean to spy is
|
||||||
|
* inferred from the type of the annotated field. If multiple candidates exist in
|
||||||
|
* the {@code ApplicationContext}, a {@code @Qualifier} annotation can be declared
|
||||||
|
* on the field to help disambiguate. In the absence of a {@code @Qualifier}
|
||||||
|
* annotation, the name of the annotated field will be used as a <em>fallback
|
||||||
|
* qualifier</em>. Alternatively, you can explicitly specify a bean name to spy
|
||||||
|
* by setting the {@link #value() value} or {@link #name() name} attribute. If a
|
||||||
|
* bean name is specified, it is required that a target bean with that name has
|
||||||
|
* been previously registered in the application context.
|
||||||
|
*
|
||||||
|
* <p>When {@code @MockitoSpyBean} is declared at the type level, the type of bean
|
||||||
|
* (or beans) to spy must be supplied via the {@link #types() types} attribute.
|
||||||
|
* If multiple candidates exist in the {@code ApplicationContext}, you can
|
||||||
|
* explicitly specify a bean name to spy by setting the {@link #name() name}
|
||||||
|
* attribute. Note, however, that the {@code types} attribute must contain a
|
||||||
|
* single type if an explicit bean {@code name} is configured.
|
||||||
*
|
*
|
||||||
* <p>A spy cannot be created for components which are known to the application
|
* <p>A spy cannot be created for components which are known to the application
|
||||||
* context but are not beans — for example, components
|
* context but are not beans — for example, components
|
||||||
|
@ -56,24 +78,33 @@ 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 @MockitoSpyBean} fields will be inherited from an enclosing test class by default.
|
* <p>{@code @MockitoSpyBean} fields and type-level {@code @MockitoSpyBean} 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 @MockitoSpyBean} may be used as a <em>meta-annotation</em> to create
|
||||||
|
* custom <em>composed annotations</em> — for example, to define common spy
|
||||||
|
* configuration in a single annotation that can be reused across a test suite.
|
||||||
|
* {@code @MockitoSpyBean} can also be used as a <em>{@linkplain Repeatable repeatable}</em>
|
||||||
|
* annotation at the type level — for example, to spy on 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.MockitoBean @MockitoBean
|
* @see org.springframework.test.context.bean.override.mockito.MockitoBean @MockitoBean
|
||||||
* @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(MockitoSpyBeans.class)
|
||||||
@BeanOverride(MockitoBeanOverrideProcessor.class)
|
@BeanOverride(MockitoBeanOverrideProcessor.class)
|
||||||
public @interface MockitoSpyBean {
|
public @interface MockitoSpyBean {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alias for {@link #name()}.
|
* Alias for {@link #name() name}.
|
||||||
* <p>Intended to be used when no other attributes are needed — for
|
* <p>Intended to be used when no other attributes are needed — for
|
||||||
* example, {@code @MockitoSpyBean("customBeanName")}.
|
* example, {@code @MockitoSpyBean("customBeanName")}.
|
||||||
* @see #name()
|
* @see #name()
|
||||||
|
@ -84,13 +115,27 @@ public @interface MockitoSpyBean {
|
||||||
/**
|
/**
|
||||||
* Name of the bean to spy.
|
* Name of the bean to spy.
|
||||||
* <p>If left unspecified, the bean to spy is selected according to the
|
* <p>If left unspecified, the bean to spy 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 MockitoSpyBean class-level documentation} for details.
|
* qualifiers into account if necessary. See the {@linkplain MockitoSpyBean
|
||||||
|
* class-level documentation} for details.
|
||||||
* @see #value()
|
* @see #value()
|
||||||
*/
|
*/
|
||||||
@AliasFor("value")
|
@AliasFor("value")
|
||||||
String name() default "";
|
String name() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One or more types to spy.
|
||||||
|
* <p>Defaults to none.
|
||||||
|
* <p>Each type specified will result in a spy being created and registered
|
||||||
|
* with the {@code ApplicationContext}.
|
||||||
|
* <p>Types must be omitted when the annotation is used on a field.
|
||||||
|
* <p>When {@code @MockitoSpyBean} also defines a {@link #name name}, this
|
||||||
|
* attribute can only contain a single value.
|
||||||
|
* @return the types to spy
|
||||||
|
* @since 6.2.3
|
||||||
|
*/
|
||||||
|
Class<?>[] types() default {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The reset mode to apply to the spied bean.
|
* The reset mode to apply to the spied bean.
|
||||||
* <p>The default is {@link MockReset#AFTER} meaning that spies are automatically
|
* <p>The default is {@link MockReset#AFTER} meaning that spies are automatically
|
||||||
|
|
|
@ -48,7 +48,11 @@ class MockitoSpyBeanOverrideHandler extends AbstractMockitoBeanOverrideHandler {
|
||||||
new SpringAopBypassingVerificationStartedListener();
|
new SpringAopBypassingVerificationStartedListener();
|
||||||
|
|
||||||
|
|
||||||
MockitoSpyBeanOverrideHandler(Field field, ResolvableType typeToSpy, MockitoSpyBean spyBean) {
|
MockitoSpyBeanOverrideHandler(ResolvableType typeToSpy, MockitoSpyBean spyBean) {
|
||||||
|
this(null, typeToSpy, spyBean);
|
||||||
|
}
|
||||||
|
|
||||||
|
MockitoSpyBeanOverrideHandler(@Nullable Field field, ResolvableType typeToSpy, MockitoSpyBean spyBean) {
|
||||||
super(field, typeToSpy, (StringUtils.hasText(spyBean.name()) ? spyBean.name() : null),
|
super(field, typeToSpy, (StringUtils.hasText(spyBean.name()) ? spyBean.name() : null),
|
||||||
BeanOverrideStrategy.WRAP, spyBean.reset());
|
BeanOverrideStrategy.WRAP, spyBean.reset());
|
||||||
Assert.notNull(typeToSpy, "typeToSpy must not be null");
|
Assert.notNull(typeToSpy, "typeToSpy must not be null");
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2025 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.test.context.bean.override.mockito;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container for {@link MockitoSpyBean @MockitoSpyBean} annotations which allows
|
||||||
|
* {@code @MockitoSpyBean} to be used as a {@linkplain java.lang.annotation.Repeatable
|
||||||
|
* repeatable annotation} at the type level — for example, on test classes
|
||||||
|
* or interfaces implemented by test classes.
|
||||||
|
*
|
||||||
|
* @author Sam Brannen
|
||||||
|
* @since 6.2.3
|
||||||
|
*/
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
public @interface MockitoSpyBeans {
|
||||||
|
|
||||||
|
MockitoSpyBean[] value();
|
||||||
|
|
||||||
|
}
|
|
@ -105,6 +105,19 @@ class MockitoBeanOverrideProcessorTests {
|
||||||
@Nested
|
@Nested
|
||||||
class CreateHandlersTests {
|
class CreateHandlersTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void otherAnnotationThrows() {
|
||||||
|
Annotation annotation = getClass().getAnnotation(Nested.class);
|
||||||
|
|
||||||
|
assertThatIllegalStateException()
|
||||||
|
.isThrownBy(() -> processor.createHandlers(annotation, getClass()))
|
||||||
|
.withMessage("Invalid annotation passed to MockitoBeanOverrideProcessor: expected either " +
|
||||||
|
"@MockitoBean or @MockitoSpyBean on test class %s", getClass().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class MockitoBeanTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void missingTypes() {
|
void missingTypes() {
|
||||||
Class<?> testClass = MissingTypesTestCase.class;
|
Class<?> testClass = MissingTypesTestCase.class;
|
||||||
|
@ -193,4 +206,97 @@ class MockitoBeanOverrideProcessorTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class MockitoSpyBeanTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void missingTypes() {
|
||||||
|
Class<?> testClass = MissingTypesTestCase.class;
|
||||||
|
MockitoSpyBean annotation = testClass.getAnnotation(MockitoSpyBean.class);
|
||||||
|
|
||||||
|
assertThatIllegalStateException()
|
||||||
|
.isThrownBy(() -> processor.createHandlers(annotation, testClass))
|
||||||
|
.withMessage("The @MockitoSpyBean 'types' attribute must not be empty when declared on a class");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void nameNotSupportedWithMultipleTypes() {
|
||||||
|
Class<?> testClass = NameNotSupportedWithMultipleTypesTestCase.class;
|
||||||
|
MockitoSpyBean annotation = testClass.getAnnotation(MockitoSpyBean.class);
|
||||||
|
|
||||||
|
assertThatIllegalStateException()
|
||||||
|
.isThrownBy(() -> processor.createHandlers(annotation, testClass))
|
||||||
|
.withMessage("The @MockitoSpyBean 'name' attribute cannot be used when mocking multiple types");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void singleSpyByType() {
|
||||||
|
Class<?> testClass = SingleSpyByTypeTestCase.class;
|
||||||
|
MockitoSpyBean annotation = testClass.getAnnotation(MockitoSpyBean.class);
|
||||||
|
List<BeanOverrideHandler> handlers = processor.createHandlers(annotation, testClass);
|
||||||
|
|
||||||
|
assertThat(handlers).singleElement().isInstanceOfSatisfying(MockitoSpyBeanOverrideHandler.class, handler -> {
|
||||||
|
assertThat(handler.getField()).isNull();
|
||||||
|
assertThat(handler.getBeanName()).isNull();
|
||||||
|
assertThat(handler.getBeanType().resolve()).isEqualTo(Integer.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void singleSpyByName() {
|
||||||
|
Class<?> testClass = SingleSpyByNameTestCase.class;
|
||||||
|
MockitoSpyBean annotation = testClass.getAnnotation(MockitoSpyBean.class);
|
||||||
|
List<BeanOverrideHandler> handlers = processor.createHandlers(annotation, testClass);
|
||||||
|
|
||||||
|
assertThat(handlers).singleElement().isInstanceOfSatisfying(MockitoSpyBeanOverrideHandler.class, handler -> {
|
||||||
|
assertThat(handler.getField()).isNull();
|
||||||
|
assertThat(handler.getBeanName()).isEqualTo("enigma");
|
||||||
|
assertThat(handler.getBeanType().resolve()).isEqualTo(Integer.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void multipleSpies() {
|
||||||
|
Class<?> testClass = MultipleSpiesTestCase.class;
|
||||||
|
MockitoSpyBean annotation = testClass.getAnnotation(MockitoSpyBean.class);
|
||||||
|
List<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);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@MockitoSpyBean
|
||||||
|
static class MissingTypesTestCase {
|
||||||
|
}
|
||||||
|
|
||||||
|
@MockitoSpyBean(name = "bogus", types = { Integer.class, Float.class })
|
||||||
|
static class NameNotSupportedWithMultipleTypesTestCase {
|
||||||
|
}
|
||||||
|
|
||||||
|
@MockitoSpyBean(types = Integer.class)
|
||||||
|
static class SingleSpyByTypeTestCase {
|
||||||
|
}
|
||||||
|
|
||||||
|
@MockitoSpyBean(name = "enigma", types = Integer.class)
|
||||||
|
static class SingleSpyByNameTestCase {
|
||||||
|
}
|
||||||
|
|
||||||
|
@MockitoSpyBean(types = { Integer.class, Float.class })
|
||||||
|
static class MultipleSpiesTestCase {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,10 +14,10 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.test.context.bean.override.mockito.mockbeans;
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
@MockitoBean(types = Service01.class)
|
@MockitoBean(types = Service01.class)
|
||||||
interface TestInterface01 {
|
interface MockTestInterface01 {
|
||||||
}
|
}
|
|
@ -14,10 +14,10 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.test.context.bean.override.mockito.mockbeans;
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
@MockitoBean(types = Service08.class)
|
@MockitoBean(types = Service08.class)
|
||||||
interface TestInterface08 {
|
interface MockTestInterface08 {
|
||||||
}
|
}
|
|
@ -14,10 +14,10 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.test.context.bean.override.mockito.mockbeans;
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
|
|
||||||
@MockitoBean(types = Service11.class)
|
@MockitoBean(types = Service11.class)
|
||||||
interface TestInterface11 {
|
interface MockTestInterface11 {
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.test.context.bean.override.mockito.mockbeans;
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
@ -30,6 +30,8 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.BDDMockito.given;
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.springframework.test.mockito.MockitoAssertions.assertIsMock;
|
||||||
|
import static org.springframework.test.mockito.MockitoAssertions.assertIsNotMock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Integration tests for {@link MockitoBeans @MockitoBeans} and
|
* Integration tests for {@link MockitoBeans @MockitoBeans} and
|
||||||
|
@ -69,12 +71,18 @@ class MockitoBeansByNameIntegrationTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void checkMocksAndStandardBean() {
|
void checkMocksAndStandardBean() {
|
||||||
|
assertIsMock(s1, "s1");
|
||||||
|
assertIsMock(s2, "s2");
|
||||||
|
assertIsMock(service3, "service3");
|
||||||
|
assertIsNotMock(service4, "service4");
|
||||||
|
|
||||||
assertThat(s1.greeting()).isEqualTo("mock 1");
|
assertThat(s1.greeting()).isEqualTo("mock 1");
|
||||||
assertThat(s2.greeting()).isEqualTo("mock 2");
|
assertThat(s2.greeting()).isEqualTo("mock 2");
|
||||||
assertThat(service3.greeting()).isEqualTo("mock 3");
|
assertThat(service3.greeting()).isEqualTo("mock 3");
|
||||||
assertThat(service4.greeting()).isEqualTo("prod 4");
|
assertThat(service4.greeting()).isEqualTo("prod 4");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
static class Config {
|
static class Config {
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.test.context.bean.override.mockito.mockbeans;
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
|
@ -27,6 +27,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.BDDMockito.given;
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.springframework.test.mockito.MockitoAssertions.assertIsMock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Integration tests for {@link MockitoBeans @MockitoBeans} and
|
* Integration tests for {@link MockitoBeans @MockitoBeans} and
|
||||||
|
@ -42,7 +43,7 @@ import static org.mockito.BDDMockito.given;
|
||||||
@MockitoBean(types = {Service04.class, Service05.class})
|
@MockitoBean(types = {Service04.class, Service05.class})
|
||||||
@SharedMocks // Intentionally declared between local @MockitoBean declarations
|
@SharedMocks // Intentionally declared between local @MockitoBean declarations
|
||||||
@MockitoBean(types = Service06.class)
|
@MockitoBean(types = Service06.class)
|
||||||
class MockitoBeansByTypeIntegrationTests implements TestInterface01 {
|
class MockitoBeansByTypeIntegrationTests implements MockTestInterface01 {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
Service01 service01;
|
Service01 service01;
|
||||||
|
@ -79,6 +80,14 @@ class MockitoBeansByTypeIntegrationTests implements TestInterface01 {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void checkMocks() {
|
void checkMocks() {
|
||||||
|
assertIsMock(service01, "service01");
|
||||||
|
assertIsMock(service02, "service02");
|
||||||
|
assertIsMock(service03, "service03");
|
||||||
|
assertIsMock(service04, "service04");
|
||||||
|
assertIsMock(service05, "service05");
|
||||||
|
assertIsMock(service06, "service06");
|
||||||
|
assertIsMock(service07, "service07");
|
||||||
|
|
||||||
assertThat(service01.greeting()).isEqualTo("mock 01");
|
assertThat(service01.greeting()).isEqualTo("mock 01");
|
||||||
assertThat(service02.greeting()).isEqualTo("mock 02");
|
assertThat(service02.greeting()).isEqualTo("mock 02");
|
||||||
assertThat(service03.greeting()).isEqualTo("mock 03");
|
assertThat(service03.greeting()).isEqualTo("mock 03");
|
||||||
|
@ -90,7 +99,7 @@ class MockitoBeansByTypeIntegrationTests implements TestInterface01 {
|
||||||
|
|
||||||
|
|
||||||
@MockitoBean(types = Service09.class)
|
@MockitoBean(types = Service09.class)
|
||||||
class BaseTestCase implements TestInterface08 {
|
class BaseTestCase implements MockTestInterface08 {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
Service08 service08;
|
Service08 service08;
|
||||||
|
@ -104,7 +113,7 @@ class MockitoBeansByTypeIntegrationTests implements TestInterface01 {
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
@MockitoBean(types = Service12.class)
|
@MockitoBean(types = Service12.class)
|
||||||
class NestedTests extends BaseTestCase implements TestInterface11 {
|
class NestedTests extends BaseTestCase implements MockTestInterface11 {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
Service11 service11;
|
Service11 service11;
|
||||||
|
@ -128,6 +137,20 @@ class MockitoBeansByTypeIntegrationTests implements TestInterface01 {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void checkMocks() {
|
void checkMocks() {
|
||||||
|
assertIsMock(service01, "service01");
|
||||||
|
assertIsMock(service02, "service02");
|
||||||
|
assertIsMock(service03, "service03");
|
||||||
|
assertIsMock(service04, "service04");
|
||||||
|
assertIsMock(service05, "service05");
|
||||||
|
assertIsMock(service06, "service06");
|
||||||
|
assertIsMock(service07, "service07");
|
||||||
|
assertIsMock(service08, "service08");
|
||||||
|
assertIsMock(service09, "service09");
|
||||||
|
assertIsMock(service10, "service10");
|
||||||
|
assertIsMock(service11, "service11");
|
||||||
|
assertIsMock(service12, "service12");
|
||||||
|
assertIsMock(service13, "service13");
|
||||||
|
|
||||||
assertThat(service01.greeting()).isEqualTo("mock 01");
|
assertThat(service01.greeting()).isEqualTo("mock 01");
|
||||||
assertThat(service02.greeting()).isEqualTo("mock 02");
|
assertThat(service02.greeting()).isEqualTo("mock 02");
|
||||||
assertThat(service03.greeting()).isEqualTo("mock 03");
|
assertThat(service03.greeting()).isEqualTo("mock 03");
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.test.context.bean.override.mockito.mockbeans;
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2025 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.test.context.bean.override.example.ExampleService;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoSpyBeans;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.springframework.test.mockito.MockitoAssertions.assertIsNotMock;
|
||||||
|
import static org.springframework.test.mockito.MockitoAssertions.assertIsNotSpy;
|
||||||
|
import static org.springframework.test.mockito.MockitoAssertions.assertIsSpy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests for {@link MockitoSpyBeans @MockitoSpyBeans} and
|
||||||
|
* {@link MockitoSpyBean @MockitoSpyBean} declared "by name" at the class level
|
||||||
|
* as a repeatable annotation.
|
||||||
|
*
|
||||||
|
* @author Sam Brannen
|
||||||
|
* @since 6.2.3
|
||||||
|
* @see <a href="https://github.com/spring-projects/spring-framework/issues/34408">gh-34408</a>
|
||||||
|
* @see MockitoSpyBeansByTypeIntegrationTests
|
||||||
|
*/
|
||||||
|
@SpringJUnitConfig
|
||||||
|
@MockitoSpyBean(name = "s1", types = ExampleService.class)
|
||||||
|
@MockitoSpyBean(name = "s2", types = ExampleService.class)
|
||||||
|
class MockitoSpyBeansByNameIntegrationTests {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ExampleService s1;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ExampleService s2;
|
||||||
|
|
||||||
|
@MockitoSpyBean(name = "s3")
|
||||||
|
ExampleService service3;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("s4")
|
||||||
|
ExampleService service4;
|
||||||
|
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void configureSpies() {
|
||||||
|
given(s1.greeting()).willReturn("spy 1");
|
||||||
|
given(s2.greeting()).willReturn("spy 2");
|
||||||
|
given(service3.greeting()).willReturn("spy 3");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void checkSpiesAndStandardBean() {
|
||||||
|
assertIsSpy(s1, "s1");
|
||||||
|
assertIsSpy(s2, "s2");
|
||||||
|
assertIsSpy(service3, "service3");
|
||||||
|
assertIsNotMock(service4, "service4");
|
||||||
|
assertIsNotSpy(service4, "service4");
|
||||||
|
|
||||||
|
assertThat(s1.greeting()).isEqualTo("spy 1");
|
||||||
|
assertThat(s2.greeting()).isEqualTo("spy 2");
|
||||||
|
assertThat(service3.greeting()).isEqualTo("spy 3");
|
||||||
|
assertThat(service4.greeting()).isEqualTo("prod 4");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class Config {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
ExampleService s1() {
|
||||||
|
return new ExampleService() {
|
||||||
|
@Override
|
||||||
|
public String greeting() {
|
||||||
|
return "prod 1";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
ExampleService s2() {
|
||||||
|
return new ExampleService() {
|
||||||
|
@Override
|
||||||
|
public String greeting() {
|
||||||
|
return "prod 2";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
ExampleService s3() {
|
||||||
|
return new ExampleService() {
|
||||||
|
@Override
|
||||||
|
public String greeting() {
|
||||||
|
return "prod 3";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
ExampleService s4() {
|
||||||
|
return () -> "prod 4";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,307 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2025 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoSpyBeans;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.springframework.test.mockito.MockitoAssertions.assertIsSpy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests for {@link MockitoSpyBeans @MockitoSpyBeans} and
|
||||||
|
* {@link MockitoSpyBean @MockitoSpyBean} declared "by type" at the class level,
|
||||||
|
* as a repeatable annotation, and via a custom composed annotation.
|
||||||
|
*
|
||||||
|
* @author Sam Brannen
|
||||||
|
* @since 6.2.3
|
||||||
|
* @see <a href="https://github.com/spring-projects/spring-framework/issues/34408">gh-34408</a>
|
||||||
|
* @see MockitoSpyBeansByNameIntegrationTests
|
||||||
|
*/
|
||||||
|
@SpringJUnitConfig
|
||||||
|
@MockitoSpyBean(types = {Service04.class, Service05.class})
|
||||||
|
@SharedSpies // Intentionally declared between local @MockitoSpyBean declarations
|
||||||
|
@MockitoSpyBean(types = Service06.class)
|
||||||
|
class MockitoSpyBeansByTypeIntegrationTests implements SpyTestInterface01 {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
Service01 service01;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
Service02 service02;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
Service03 service03;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
Service04 service04;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
Service05 service05;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
Service06 service06;
|
||||||
|
|
||||||
|
@MockitoSpyBean
|
||||||
|
Service07 service07;
|
||||||
|
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void configureSpies() {
|
||||||
|
given(service01.greeting()).willReturn("spy 01");
|
||||||
|
given(service02.greeting()).willReturn("spy 02");
|
||||||
|
given(service03.greeting()).willReturn("spy 03");
|
||||||
|
given(service04.greeting()).willReturn("spy 04");
|
||||||
|
given(service05.greeting()).willReturn("spy 05");
|
||||||
|
given(service06.greeting()).willReturn("spy 06");
|
||||||
|
given(service07.greeting()).willReturn("spy 07");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void checkSpies() {
|
||||||
|
assertIsSpy(service01, "service01");
|
||||||
|
assertIsSpy(service02, "service02");
|
||||||
|
assertIsSpy(service03, "service03");
|
||||||
|
assertIsSpy(service04, "service04");
|
||||||
|
assertIsSpy(service05, "service05");
|
||||||
|
assertIsSpy(service06, "service06");
|
||||||
|
assertIsSpy(service07, "service07");
|
||||||
|
|
||||||
|
assertThat(service01.greeting()).isEqualTo("spy 01");
|
||||||
|
assertThat(service02.greeting()).isEqualTo("spy 02");
|
||||||
|
assertThat(service03.greeting()).isEqualTo("spy 03");
|
||||||
|
assertThat(service04.greeting()).isEqualTo("spy 04");
|
||||||
|
assertThat(service05.greeting()).isEqualTo("spy 05");
|
||||||
|
assertThat(service06.greeting()).isEqualTo("spy 06");
|
||||||
|
assertThat(service07.greeting()).isEqualTo("spy 07");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@MockitoSpyBean(types = Service09.class)
|
||||||
|
class BaseTestCase implements SpyTestInterface08 {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
Service08 service08;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
Service09 service09;
|
||||||
|
|
||||||
|
@MockitoSpyBean
|
||||||
|
Service10 service10;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@MockitoSpyBean(types = Service12.class)
|
||||||
|
class NestedTests extends BaseTestCase implements SpyTestInterface11 {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
Service11 service11;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
Service12 service12;
|
||||||
|
|
||||||
|
@MockitoSpyBean
|
||||||
|
Service13 service13;
|
||||||
|
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void configureSpies() {
|
||||||
|
given(service08.greeting()).willReturn("spy 08");
|
||||||
|
given(service09.greeting()).willReturn("spy 09");
|
||||||
|
given(service10.greeting()).willReturn("spy 10");
|
||||||
|
given(service11.greeting()).willReturn("spy 11");
|
||||||
|
given(service12.greeting()).willReturn("spy 12");
|
||||||
|
given(service13.greeting()).willReturn("spy 13");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void checkSpies() {
|
||||||
|
assertIsSpy(service01, "service01");
|
||||||
|
assertIsSpy(service02, "service02");
|
||||||
|
assertIsSpy(service03, "service03");
|
||||||
|
assertIsSpy(service04, "service04");
|
||||||
|
assertIsSpy(service05, "service05");
|
||||||
|
assertIsSpy(service06, "service06");
|
||||||
|
assertIsSpy(service07, "service07");
|
||||||
|
assertIsSpy(service08, "service08");
|
||||||
|
assertIsSpy(service09, "service09");
|
||||||
|
assertIsSpy(service10, "service10");
|
||||||
|
assertIsSpy(service11, "service11");
|
||||||
|
assertIsSpy(service12, "service12");
|
||||||
|
assertIsSpy(service13, "service13");
|
||||||
|
|
||||||
|
assertThat(service01.greeting()).isEqualTo("spy 01");
|
||||||
|
assertThat(service02.greeting()).isEqualTo("spy 02");
|
||||||
|
assertThat(service03.greeting()).isEqualTo("spy 03");
|
||||||
|
assertThat(service04.greeting()).isEqualTo("spy 04");
|
||||||
|
assertThat(service05.greeting()).isEqualTo("spy 05");
|
||||||
|
assertThat(service06.greeting()).isEqualTo("spy 06");
|
||||||
|
assertThat(service07.greeting()).isEqualTo("spy 07");
|
||||||
|
assertThat(service08.greeting()).isEqualTo("spy 08");
|
||||||
|
assertThat(service09.greeting()).isEqualTo("spy 09");
|
||||||
|
assertThat(service10.greeting()).isEqualTo("spy 10");
|
||||||
|
assertThat(service11.greeting()).isEqualTo("spy 11");
|
||||||
|
assertThat(service12.greeting()).isEqualTo("spy 12");
|
||||||
|
assertThat(service13.greeting()).isEqualTo("spy 13");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class Config {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Service01 service01() {
|
||||||
|
return new Service01() {
|
||||||
|
@Override
|
||||||
|
public String greeting() {
|
||||||
|
return "prod 1";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Service02 service02() {
|
||||||
|
return new Service02() {
|
||||||
|
@Override
|
||||||
|
public String greeting() {
|
||||||
|
return "prod 2";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Service03 service03() {
|
||||||
|
return new Service03() {
|
||||||
|
@Override
|
||||||
|
public String greeting() {
|
||||||
|
return "prod 3";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Service04 service04() {
|
||||||
|
return new Service04() {
|
||||||
|
@Override
|
||||||
|
public String greeting() {
|
||||||
|
return "prod 4";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Service05 service05() {
|
||||||
|
return new Service05() {
|
||||||
|
@Override
|
||||||
|
public String greeting() {
|
||||||
|
return "prod 5";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Service06 service06() {
|
||||||
|
return new Service06() {
|
||||||
|
@Override
|
||||||
|
public String greeting() {
|
||||||
|
return "prod 6";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Service07 service07() {
|
||||||
|
return new Service07() {
|
||||||
|
@Override
|
||||||
|
public String greeting() {
|
||||||
|
return "prod 7";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Service08 service08() {
|
||||||
|
return new Service08() {
|
||||||
|
@Override
|
||||||
|
public String greeting() {
|
||||||
|
return "prod 8";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Service09 service09() {
|
||||||
|
return new Service09() {
|
||||||
|
@Override
|
||||||
|
public String greeting() {
|
||||||
|
return "prod 9";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Service10 service10() {
|
||||||
|
return new Service10() {
|
||||||
|
@Override
|
||||||
|
public String greeting() {
|
||||||
|
return "prod 10";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Service11 service11() {
|
||||||
|
return new Service11() {
|
||||||
|
@Override
|
||||||
|
public String greeting() {
|
||||||
|
return "prod 11";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Service12 service12() {
|
||||||
|
return new Service12() {
|
||||||
|
@Override
|
||||||
|
public String greeting() {
|
||||||
|
return "prod 12";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Service13 service13() {
|
||||||
|
return new Service13() {
|
||||||
|
@Override
|
||||||
|
public String greeting() {
|
||||||
|
return "prod 13";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2025 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.core.ResolvableType;
|
||||||
|
import org.springframework.test.context.bean.override.BeanOverrideHandler;
|
||||||
|
import org.springframework.test.context.bean.override.BeanOverrideTestUtils;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoSpyBeans;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link MockitoSpyBeans @MockitoSpyBeans}: {@link MockitoSpyBean @MockitoSpyBean}
|
||||||
|
* declared at the class level, as a repeatable annotation, and via a custom composed
|
||||||
|
* annotation.
|
||||||
|
*
|
||||||
|
* @author Sam Brannen
|
||||||
|
* @since 6.2.3
|
||||||
|
* @see <a href="https://github.com/spring-projects/spring-framework/issues/34408">gh-34408</a>
|
||||||
|
*/
|
||||||
|
class MockitoSpyBeansTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void registrationOrderForTopLevelClass() {
|
||||||
|
Stream<Class<?>> mockedServices = getRegisteredMockTypes(MockitoSpyBeansByTypeIntegrationTests.class);
|
||||||
|
assertThat(mockedServices).containsExactly(
|
||||||
|
Service01.class, Service02.class, Service03.class, Service04.class,
|
||||||
|
Service05.class, Service06.class, Service07.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void registrationOrderForNestedClass() {
|
||||||
|
Stream<Class<?>> mockedServices = getRegisteredMockTypes(MockitoSpyBeansByTypeIntegrationTests.NestedTests.class);
|
||||||
|
assertThat(mockedServices).containsExactly(
|
||||||
|
Service01.class, Service02.class, Service03.class, Service04.class,
|
||||||
|
Service05.class, Service06.class, Service07.class, Service08.class,
|
||||||
|
Service09.class, Service10.class, Service11.class, Service12.class,
|
||||||
|
Service13.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static Stream<Class<?>> getRegisteredMockTypes(Class<?> testClass) {
|
||||||
|
return BeanOverrideTestUtils.findAllHandlers(testClass)
|
||||||
|
.stream()
|
||||||
|
.map(BeanOverrideHandler::getBeanType)
|
||||||
|
.map(ResolvableType::getRawClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.test.context.bean.override.mockito.mockbeans;
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
interface Service {
|
interface Service {
|
||||||
String greeting();
|
String greeting();
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.test.context.bean.override.mockito.mockbeans;
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
interface Service01 extends Service {
|
interface Service01 extends Service {
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.test.context.bean.override.mockito.mockbeans;
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
interface Service02 extends Service {
|
interface Service02 extends Service {
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.test.context.bean.override.mockito.mockbeans;
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
interface Service03 extends Service {
|
interface Service03 extends Service {
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.test.context.bean.override.mockito.mockbeans;
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
interface Service04 extends Service {
|
interface Service04 extends Service {
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.test.context.bean.override.mockito.mockbeans;
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
interface Service05 extends Service {
|
interface Service05 extends Service {
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.test.context.bean.override.mockito.mockbeans;
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
interface Service06 extends Service {
|
interface Service06 extends Service {
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.test.context.bean.override.mockito.mockbeans;
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
interface Service07 extends Service {
|
interface Service07 extends Service {
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.test.context.bean.override.mockito.mockbeans;
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
interface Service08 extends Service {
|
interface Service08 extends Service {
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.test.context.bean.override.mockito.mockbeans;
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
interface Service09 extends Service {
|
interface Service09 extends Service {
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.test.context.bean.override.mockito.mockbeans;
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
interface Service10 extends Service {
|
interface Service10 extends Service {
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.test.context.bean.override.mockito.mockbeans;
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
interface Service11 extends Service {
|
interface Service11 extends Service {
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.test.context.bean.override.mockito.mockbeans;
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
interface Service12 extends Service {
|
interface Service12 extends Service {
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.test.context.bean.override.mockito.mockbeans;
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
interface Service13 extends Service {
|
interface Service13 extends Service {
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.test.context.bean.override.mockito.mockbeans;
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2025 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
|
||||||
|
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@MockitoSpyBean(types = Service02.class)
|
||||||
|
@MockitoSpyBean(types = Service03.class)
|
||||||
|
@interface SharedSpies {
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2025 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
|
||||||
|
|
||||||
|
@MockitoSpyBean(types = Service01.class)
|
||||||
|
interface SpyTestInterface01 {
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2025 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
|
||||||
|
|
||||||
|
@MockitoSpyBean(types = Service08.class)
|
||||||
|
interface SpyTestInterface08 {
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2025 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.test.context.bean.override.mockito.typelevel;
|
||||||
|
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
|
||||||
|
|
||||||
|
@MockitoSpyBean(types = Service11.class)
|
||||||
|
interface SpyTestInterface11 {
|
||||||
|
}
|
|
@ -31,10 +31,12 @@ public abstract class MockitoAssertions {
|
||||||
|
|
||||||
public static void assertIsMock(Object obj) {
|
public static void assertIsMock(Object obj) {
|
||||||
assertThat(isMock(obj)).as("is a Mockito mock").isTrue();
|
assertThat(isMock(obj)).as("is a Mockito mock").isTrue();
|
||||||
|
assertIsNotSpy(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void assertIsMock(Object obj, String message) {
|
public static void assertIsMock(Object obj, String message) {
|
||||||
assertThat(isMock(obj)).as("%s is a Mockito mock", message).isTrue();
|
assertThat(isMock(obj)).as("%s is a Mockito mock", message).isTrue();
|
||||||
|
assertIsNotSpy(obj, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void assertIsNotMock(Object obj) {
|
public static void assertIsNotMock(Object obj) {
|
||||||
|
|
Loading…
Reference in New Issue