diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoAnnotationDetector.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoAnnotationDetector.java new file mode 100644 index 000000000000..2d17513f1226 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoAnnotationDetector.java @@ -0,0 +1,66 @@ +/* + * 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.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; + +import org.springframework.util.ReflectionUtils; + +/** + * Utility class that detects {@code org.mockito} annotations as well as the + * annotations in this package (like {@link MockitoBeanSettings @MockitoBeanSettings}). + * + * @author Simon Baslé + * @author Sam Brannen + */ +class MockitoAnnotationDetector { + + private static final String MOCKITO_BEAN_PACKAGE = MockitoBeanSettings.class.getPackageName(); + + private static final String ORG_MOCKITO_PACKAGE = "org.mockito"; + + private static final Predicate isMockitoAnnotation = annotation -> { + String packageName = annotation.annotationType().getPackageName(); + return (packageName.startsWith(MOCKITO_BEAN_PACKAGE) || + packageName.startsWith(ORG_MOCKITO_PACKAGE)); + }; + + static boolean hasMockitoAnnotations(Class testClass) { + if (isAnnotated(testClass)) { + 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. + AtomicBoolean found = new AtomicBoolean(); + ReflectionUtils.doWithFields(testClass, field -> found.set(true), MockitoAnnotationDetector::isAnnotated); + return found.get(); + } + + private static boolean isAnnotated(AnnotatedElement annotatedElement) { + return Arrays.stream(annotatedElement.getAnnotations()).anyMatch(isMockitoAnnotation); + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoResetTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoResetTestExecutionListener.java index 46cf654c1858..2785fd7ab186 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoResetTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoResetTestExecutionListener.java @@ -29,7 +29,6 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.core.NativeDetector; import org.springframework.core.Ordered; import org.springframework.lang.Nullable; import org.springframework.test.context.TestContext; @@ -58,14 +57,16 @@ public int getOrder() { @Override public void beforeTestMethod(TestContext testContext) throws Exception { - if (MockitoTestExecutionListener.mockitoPresent && !NativeDetector.inNativeImage()) { + Class testClass = testContext.getTestClass(); + if (MockitoTestExecutionListener.mockitoPresent && MockitoAnnotationDetector.hasMockitoAnnotations(testClass)) { resetMocks(testContext.getApplicationContext(), MockReset.BEFORE); } } @Override public void afterTestMethod(TestContext testContext) throws Exception { - if (MockitoTestExecutionListener.mockitoPresent && !NativeDetector.inNativeImage()) { + Class testClass = testContext.getTestClass(); + if (MockitoTestExecutionListener.mockitoPresent && MockitoAnnotationDetector.hasMockitoAnnotations(testClass)) { resetMocks(testContext.getApplicationContext(), MockReset.AFTER); } } 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 46d7799889b7..0630e355cddb 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 @@ -16,12 +16,6 @@ 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.util.function.Predicate; - import org.mockito.Mockito; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; @@ -31,7 +25,6 @@ import org.springframework.test.context.support.AbstractTestExecutionListener; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.util.ClassUtils; -import org.springframework.util.ReflectionUtils; /** * {@code TestExecutionListener} that enables {@link MockitoBean @MockitoBean} @@ -127,42 +120,4 @@ private void closeMocks(TestContext testContext) throws Exception { } } - - /** - * Utility class that detects {@code org.mockito} annotations as well as the - * annotations in this package (like {@link MockitoBeanSettings @MockitoBeanSettings}). - */ - private static class MockitoAnnotationDetector { - - private static final String MOCKITO_BEAN_PACKAGE = MockitoBeanSettings.class.getPackageName(); - - private static final String ORG_MOCKITO_PACKAGE = "org.mockito"; - - private static final Predicate isMockitoAnnotation = annotation -> { - String packageName = annotation.annotationType().getPackageName(); - return (packageName.startsWith(MOCKITO_BEAN_PACKAGE) || - packageName.startsWith(ORG_MOCKITO_PACKAGE)); - }; - - static boolean hasMockitoAnnotations(Class testClass) { - if (isAnnotated(testClass)) { - 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. - AtomicBoolean found = new AtomicBoolean(); - ReflectionUtils.doWithFields(testClass, field -> found.set(true), MockitoAnnotationDetector::isAnnotated); - return found.get(); - } - - private static boolean isAnnotated(AnnotatedElement annotatedElement) { - return Arrays.stream(annotatedElement.getAnnotations()).anyMatch(isMockitoAnnotation); - } - } - }