Support @MockitoBean reset and MockitoSession management with @Nested tests
Prior to this commit, the MockitoResetTestExecutionListener failed to reset mocks created via @MockitoBean if the @MockitoBean field was declared in an enclosing class for a @Nested test class. In addition, the MockitoSession was not properly managed by the MockitoTestExecutionListener. This commit addresses those issue as follows. 1) The hasMockitoAnnotations() utility method has been overhauled so that it finds Mockito annotations not only on the current test class and on fields of the current test class but also on interfaces, superclasses, and enclosing classes for @Nested test classes as well as on fields of superclasses and enclosing classes. That allows the MockitoResetTestExecutionListener to properly detect that it needs to reset mocks for fields declared in enclosing classes for @Nested classes. 2) MockitoTestExecutionListener has been revised so that it only initializes a MockitoSession before each test method and closes the MockitoSession after each test method. In addition, it now only manages the MockitoSession when hasMockitoAnnotations() returns true for the current test class (which may be a @Nested test class). Furthermore, it no longer attempts to initialize a MockitoSession during the prepareTestInstance() callback since that results in an UnfinishedMockingSessionException for a @Nested test class due to the fact that a MockitoSession was already created for the current thread for the enclosing test class. Closes gh-33676
This commit is contained in:
@@ -16,16 +16,16 @@
|
||||
|
||||
package org.springframework.test.context.bean.override.mockito;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.TestContextAnnotationUtils;
|
||||
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@code TestExecutionListener} implementations involving
|
||||
@@ -44,8 +44,8 @@ abstract class AbstractMockitoTestExecutionListener extends AbstractTestExecutio
|
||||
|
||||
private static final String ORG_MOCKITO_PACKAGE = "org.mockito";
|
||||
|
||||
private static final Predicate<Annotation> isMockitoAnnotation = annotation -> {
|
||||
String packageName = annotation.annotationType().getPackageName();
|
||||
private static final Predicate<MergedAnnotation<?>> isMockitoAnnotation = mergedAnnotation -> {
|
||||
String packageName = mergedAnnotation.getType().getPackageName();
|
||||
return (packageName.startsWith(SPRING_MOCKITO_PACKAGE) ||
|
||||
packageName.startsWith(ORG_MOCKITO_PACKAGE));
|
||||
};
|
||||
@@ -60,25 +60,47 @@ abstract class AbstractMockitoTestExecutionListener extends AbstractTestExecutio
|
||||
return hasMockitoAnnotations(testContext.getTestClass());
|
||||
}
|
||||
|
||||
private static boolean hasMockitoAnnotations(Class<?> testClass) {
|
||||
if (isAnnotated(testClass)) {
|
||||
/**
|
||||
* Determine if Mockito annotations are declared on the supplied class, on an
|
||||
* interface it implements, on a superclass, or on an enclosing class or
|
||||
* whether a field in any such class is annotated with a Mockito annotation.
|
||||
*/
|
||||
private static boolean hasMockitoAnnotations(Class<?> clazz) {
|
||||
// Declared on the class?
|
||||
if (MergedAnnotations.from(clazz, SearchStrategy.DIRECT).stream().anyMatch(isMockitoAnnotation)) {
|
||||
return true;
|
||||
}
|
||||
// TODO Ideally we should short-circuit the search once we've found a Mockito annotation,
|
||||
// since there's no need to continue searching additional fields or further up the class
|
||||
// hierarchy; however, that is not possible with ReflectionUtils#doWithFields. Plus, the
|
||||
// previous invocation of isAnnotated(testClass) only finds annotations declared directly
|
||||
// on the test class. So, we'll likely need a completely different approach that combines
|
||||
// the "test class/interface is annotated?" and "field is annotated?" checks in a single
|
||||
// search algorithm, and we'll also need to support @Nested class hierarchies.
|
||||
AtomicBoolean found = new AtomicBoolean();
|
||||
ReflectionUtils.doWithFields(testClass,
|
||||
field -> found.set(true), AbstractMockitoTestExecutionListener::isAnnotated);
|
||||
return found.get();
|
||||
}
|
||||
|
||||
private static boolean isAnnotated(AnnotatedElement annotatedElement) {
|
||||
return Arrays.stream(annotatedElement.getAnnotations()).anyMatch(isMockitoAnnotation);
|
||||
// Declared on a field?
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (MergedAnnotations.from(field, SearchStrategy.DIRECT).stream().anyMatch(isMockitoAnnotation)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Declared on an interface?
|
||||
for (Class<?> ifc : clazz.getInterfaces()) {
|
||||
if (hasMockitoAnnotations(ifc)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Declared on a superclass?
|
||||
Class<?> superclass = clazz.getSuperclass();
|
||||
if (superclass != null & superclass != Object.class) {
|
||||
if (hasMockitoAnnotations(superclass)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Declared on an enclosing class of an inner class?
|
||||
if (TestContextAnnotationUtils.searchEnclosingClass(clazz)) {
|
||||
if (hasMockitoAnnotations(clazz.getEnclosingClass())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -63,45 +63,26 @@ public class MockitoTestExecutionListener extends AbstractMockitoTestExecutionLi
|
||||
return 1950;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareTestInstance(TestContext testContext) {
|
||||
if (mockitoPresent) {
|
||||
closeMocks(testContext);
|
||||
initMocks(testContext);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTestMethod(TestContext testContext) {
|
||||
if (mockitoPresent && Boolean.TRUE.equals(
|
||||
testContext.getAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE))) {
|
||||
closeMocks(testContext);
|
||||
if (mockitoPresent && hasMockitoAnnotations(testContext)) {
|
||||
initMocks(testContext);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTestMethod(TestContext testContext) {
|
||||
if (mockitoPresent) {
|
||||
closeMocks(testContext);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTestClass(TestContext testContext) {
|
||||
if (mockitoPresent) {
|
||||
if (mockitoPresent && hasMockitoAnnotations(testContext)) {
|
||||
closeMocks(testContext);
|
||||
}
|
||||
}
|
||||
|
||||
private static void initMocks(TestContext testContext) {
|
||||
if (hasMockitoAnnotations(testContext)) {
|
||||
Class<?> testClass = testContext.getTestClass();
|
||||
Object testInstance = testContext.getTestInstance();
|
||||
MockitoBeanSettings annotation = AnnotationUtils.findAnnotation(testClass, MockitoBeanSettings.class);
|
||||
Strictness strictness = (annotation != null ? annotation.value() : Strictness.STRICT_STUBS);
|
||||
testContext.setAttribute(MOCKITO_SESSION_ATTRIBUTE_NAME, initMockitoSession(testInstance, strictness));
|
||||
}
|
||||
Class<?> testClass = testContext.getTestClass();
|
||||
Object testInstance = testContext.getTestInstance();
|
||||
MockitoBeanSettings annotation = AnnotationUtils.findAnnotation(testClass, MockitoBeanSettings.class);
|
||||
Strictness strictness = (annotation != null ? annotation.value() : Strictness.STRICT_STUBS);
|
||||
testContext.setAttribute(MOCKITO_SESSION_ATTRIBUTE_NAME, initMockitoSession(testInstance, strictness));
|
||||
}
|
||||
|
||||
private static MockitoSession initMockitoSession(Object testInstance, Strictness strictness) {
|
||||
|
||||
Reference in New Issue
Block a user