diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-mockitobean.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-mockitobean.adoc index fc604d0f0ed..5cce74f5d88 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-mockitobean.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-mockitobean.adoc @@ -23,6 +23,9 @@ creating unnecessary contexts. ==== Each annotation also defines Mockito-specific attributes to fine-tune the mocking details. +During the test class lifecycle, Mockito is set up via the `Mockito#mockitoSession()` +mechanism. Notably, it enables `STRICT_STUBS` mode by default. This can be changed on +individual test classes with the `@MockitoBeanSettings` annotation. The `@MockitoBean` annotation uses the `REPLACE_OR_CREATE_DEFINITION` xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy for test bean overriding]. diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanSettings.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanSettings.java new file mode 100644 index 00000000000..33f0f5f8a25 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanSettings.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2024 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; + +import org.mockito.quality.Strictness; + +/** + * Configure a test class that uses {@link MockitoBean} or {@link MockitoSpyBean} + * to set up Mockito with an explicitly specified stubbing strictness. + * + * @author Simon Baslé + * @since 6.2 + * @see MockitoTestExecutionListener + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface MockitoBeanSettings { + + /** + * The stubbing strictness to apply for all Mockito mocks in the annotated + * class. + */ + Strictness value(); + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoTestExecutionListener.java index fdc82091aea..61785432dac 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoTestExecutionListener.java @@ -21,9 +21,12 @@ import java.lang.reflect.Field; import java.util.LinkedHashSet; import java.util.Set; -import org.mockito.Captor; -import org.mockito.MockitoAnnotations; +import org.mockito.BDDMockito; +import org.mockito.Mockito; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.test.context.TestContext; import org.springframework.test.context.support.AbstractTestExecutionListener; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; @@ -32,10 +35,14 @@ import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils.FieldCallback; /** - * {@code TestExecutionListener} that enables {@link MockitoBean @MockitoBean} and - * {@link MockitoSpyBean @MockitoSpyBean} support. Also triggers - * {@link MockitoAnnotations#openMocks(Object)} when any Mockito annotations are - * used, primarily to support {@link Captor @Captor} annotations. + * {@code TestExecutionListener} that enables {@link MockitoBean @MockitoBean} + * and {@link MockitoSpyBean @MockitoSpyBean} support. Also triggers Mockito set + * up of a {@link Mockito#mockitoSession() session} for each test class that + * uses these annotations (or any annotation in that package). + * + *
The {@link MockitoSession#setStrictness(Strictness) strictness} of the + * session defaults to {@link Strictness#STRICT_STUBS}. Use + * {@link MockitoBeanSettings} to specify a different strictness. * *
The automatic reset support for {@code @MockBean} and {@code @SpyBean} is
* handled by the {@link MockitoResetTestExecutionListener}.
@@ -97,37 +104,67 @@ public class MockitoTestExecutionListener extends AbstractTestExecutionListener
private void initMocks(TestContext testContext) {
if (hasMockitoAnnotations(testContext)) {
Object testInstance = testContext.getTestInstance();
- testContext.setAttribute(MOCKS_ATTRIBUTE_NAME, MockitoAnnotations.openMocks(testInstance));
+ MockitoBeanSettings annotation = AnnotationUtils.findAnnotation(testInstance.getClass(),
+ MockitoBeanSettings.class);
+ testContext.setAttribute(MOCKS_ATTRIBUTE_NAME, initMockitoSession(testInstance,
+ annotation == null ? Strictness.STRICT_STUBS: annotation.value()));
}
}
+ private MockitoSession initMockitoSession(Object testInstance, Strictness strictness) {
+ return BDDMockito.mockitoSession()
+ .initMocks(testInstance)
+ .strictness(strictness)
+ .startMocking();
+ }
+
private void closeMocks(TestContext testContext) throws Exception {
Object mocks = testContext.getAttribute(MOCKS_ATTRIBUTE_NAME);
- if (mocks instanceof AutoCloseable closeable) {
+ if (mocks instanceof MockitoSession session) {
+ session.finishMocking();
+ }
+ else if (mocks instanceof AutoCloseable closeable) {
closeable.close();
}
}
private boolean hasMockitoAnnotations(TestContext testContext) {
MockitoAnnotationCollector collector = new MockitoAnnotationCollector();
- ReflectionUtils.doWithFields(testContext.getTestClass(), collector);
+ collector.collect(testContext.getTestClass());
return collector.hasAnnotations();
}
/**
- * {@link FieldCallback} that collects Mockito annotations.
+ * Utility class that collects {@code org.mockito} annotations and the
+ * annotations in this package (like {@link MockitoBeanSettings}).
*/
private static final class MockitoAnnotationCollector implements FieldCallback {
+ private static final String MOCKITO_BEAN_PACKAGE = MockitoBean.class.getPackageName();
+ private static final String ORG_MOCKITO_PACKAGE = "org.mockito";
+
private final Set