diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverride.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverride.java index f5be7dd77757..a25dff0a1d50 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverride.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverride.java @@ -16,6 +16,7 @@ package org.springframework.test.context.bean.override; +import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -24,17 +25,23 @@ /** * Mark a composed annotation as eligible for Bean Override processing. * - *

Specifying this annotation triggers the configured {@link BeanOverrideProcessor} + *

Specifying this annotation registers the configured {@link BeanOverrideProcessor} * which must be capable of handling the composed annotation and its attributes. * *

Since the composed annotation should only be applied to fields, it is - * expected that it has a {@link Target} of {@link ElementType#FIELD FIELD}. + * expected that it is meta-annotated with {@link Target @Target(ElementType.FIELD)}. + * + *

For concrete examples, see + * {@link org.springframework.test.context.bean.override.convention.TestBean @TestBean}, + * {@link org.springframework.test.context.bean.override.mockito.MockitoBean @MockitoBean}, and + * {@link org.springframework.test.context.bean.override.mockito.MockitoSpyBean @MockitoSpyBean}. * * @author Simon Baslé * @since 6.2 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) +@Documented public @interface BeanOverride { /** diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java index 04b13179fb29..ba38bce1b3ba 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java @@ -63,12 +63,12 @@ */ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered { + private static final BeanNameGenerator beanNameGenerator = DefaultBeanNameGenerator.INSTANCE; + private final Set metadata; private final BeanOverrideRegistrar overrideRegistrar; - private final BeanNameGenerator beanNameGenerator = new DefaultBeanNameGenerator(); - /** * Create a new {@code BeanOverrideBeanFactoryPostProcessor} with the supplied @@ -215,7 +215,7 @@ else if (candidateCount == 0) { "Unable to override bean: no bean definitions of type %s (as required by annotated field '%s.%s')" .formatted(overrideMetadata.getBeanType(), field.getDeclaringClass().getSimpleName(), field.getName())); } - return this.beanNameGenerator.generateBeanName(beanDefinition, registry); + return beanNameGenerator.generateBeanName(beanDefinition, registry); } Field field = overrideMetadata.getField(); @@ -230,7 +230,7 @@ private Set getExistingBeanNamesByType(ConfigurableListableBeanFactory b boolean checkAutowiredCandidate) { ResolvableType resolvableType = metadata.getBeanType(); - Class type = resolvableType.resolve(Object.class); + Class type = resolvableType.toClass(); // Start with matching bean names for type, excluding FactoryBeans. Set beanNames = new LinkedHashSet<>( diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideProcessor.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideProcessor.java index e6d10dcae56c..c1570024941c 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideProcessor.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideProcessor.java @@ -26,7 +26,7 @@ *

At least one composed annotation that is meta-annotated with * {@link BeanOverride @BeanOverride} must be a companion of this processor and * may provide additional user settings that drive how the concrete - * {@link OverrideMetadata} is configured. + * {@code OverrideMetadata} is configured. * *

Implementations are required to have a no-argument constructor and be * stateless. @@ -41,7 +41,7 @@ public interface BeanOverrideProcessor { /** * Create an {@link OverrideMetadata} instance for the given annotated field. * @param overrideAnnotation the composed annotation that declares the - * {@link BeanOverride @BeanOverride} annotation that triggers this processor + * {@link BeanOverride @BeanOverride} annotation which registers this processor * @param testClass the test class to process * @param field the annotated field * @return the {@link OverrideMetadata} instance that should handle the diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideStrategy.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideStrategy.java index 42cca189965a..72e459cb0132 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideStrategy.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideStrategy.java @@ -21,14 +21,15 @@ * * @author Simon Baslé * @author Stephane Nicoll + * @author Sam Brannen * @since 6.2 */ public enum BeanOverrideStrategy { /** * Replace a given bean definition, immediately preparing a singleton instance. - *

Fails if the original bean definition exists. To create a new bean - * definition in such a case, use {@link #REPLACE_OR_CREATE_DEFINITION}. + *

Fails if the original bean definition does not exist. To create a new bean + * definition in such a case, use {@link #REPLACE_OR_CREATE_DEFINITION} instead. */ REPLACE_DEFINITION, diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBean.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBean.java index 03d23ef9f808..0ff1c0f54b35 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBean.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBean.java @@ -26,7 +26,10 @@ import org.springframework.test.context.bean.override.BeanOverride; /** - * Mark a field to override a bean definition in the {@code BeanFactory}. + * {@code @TestBean} is an annotation that can be applied to a field in a test + * class to override a bean in the test's + * {@link org.springframework.context.ApplicationContext ApplicationContext} + * using a static factory method. * *

By default, the bean to override is inferred from the type of the * annotated field. This requires that exactly one matching bean definition is @@ -57,13 +60,12 @@ * *

Consider the following example. * - *


- * class CustomerServiceTests {
+ * 
 class CustomerServiceTests {
  *
  *     @TestBean
  *     private CustomerRepository repository;
  *
- *     // Tests
+ *     // @Test methods ...
  *
  *     private static CustomerRepository repository() {
  *         return new TestCustomerRepository();
@@ -79,15 +81,14 @@
  * 

To make things more explicit, the bean and method names can be set, * as shown in the following example. * - *


- * class CustomerServiceTests {
+ * 
 class CustomerServiceTests {
  *
  *     @TestBean(name = "customerRepository", methodName = "createTestCustomerRepository")
- *     private CustomerRepository repository;
+ *     CustomerRepository repository;
  *
- *     // Tests
+ *     // @Test methods ...
  *
- *     private static CustomerRepository createTestCustomerRepository() {
+ *     static CustomerRepository createTestCustomerRepository() {
  *         return new TestCustomerRepository();
  *     }
  * }
@@ -96,7 +97,8 @@ * @author Stephane Nicoll * @author Sam Brannen * @since 6.2 - * @see TestBeanOverrideProcessor + * @see org.springframework.test.context.bean.override.mockito.MockitoBean @MockitoBean + * @see org.springframework.test.context.bean.override.mockito.MockitoSpyBean @MockitoSpyBean */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoOverrideMetadata.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/AbstractMockitoOverrideMetadata.java similarity index 74% rename from spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoOverrideMetadata.java rename to spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/AbstractMockitoOverrideMetadata.java index 2ca61fc5ed40..6c31e4fae212 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoOverrideMetadata.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/AbstractMockitoOverrideMetadata.java @@ -19,7 +19,6 @@ import java.lang.reflect.Field; import java.util.Objects; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.SingletonBeanRegistry; import org.springframework.core.ResolvableType; import org.springframework.core.style.ToStringCreator; @@ -28,21 +27,21 @@ import org.springframework.test.context.bean.override.OverrideMetadata; /** - * Base {@link OverrideMetadata} implementation for Mockito. + * Abstract base {@link OverrideMetadata} implementation for Mockito. * * @author Phillip Webb * @author Stephane Nicoll * @author Sam Brannen * @since 6.2 */ -abstract class MockitoOverrideMetadata extends OverrideMetadata { +abstract class AbstractMockitoOverrideMetadata extends OverrideMetadata { private final MockReset reset; private final boolean proxyTargetAware; - protected MockitoOverrideMetadata(Field field, ResolvableType beanType, @Nullable String beanName, + protected AbstractMockitoOverrideMetadata(Field field, ResolvableType beanType, @Nullable String beanName, BeanOverrideStrategy strategy, @Nullable MockReset reset, boolean proxyTargetAware) { super(field, beanType, beanName, strategy); @@ -69,17 +68,20 @@ boolean isProxyTargetAware() { @Override protected void track(Object mock, SingletonBeanRegistry trackingBeanRegistry) { - MockitoBeans tracker = null; - try { - tracker = (MockitoBeans) trackingBeanRegistry.getSingleton(MockitoBeans.class.getName()); - } - catch (NoSuchBeanDefinitionException ignored) { + getMockitoBeans(trackingBeanRegistry).add(mock); + } + + private static MockitoBeans getMockitoBeans(SingletonBeanRegistry trackingBeanRegistry) { + String beanName = MockitoBeans.class.getName(); + MockitoBeans mockitoBeans = null; + if (trackingBeanRegistry.containsSingleton(beanName)) { + mockitoBeans = (MockitoBeans) trackingBeanRegistry.getSingleton(beanName); } - if (tracker == null) { - tracker = new MockitoBeans(); - trackingBeanRegistry.registerSingleton(MockitoBeans.class.getName(), tracker); + if (mockitoBeans == null) { + mockitoBeans = new MockitoBeans(); + trackingBeanRegistry.registerSingleton(beanName, mockitoBeans); } - tracker.add(mock); + return mockitoBeans; } @Override @@ -87,7 +89,7 @@ public boolean equals(@Nullable Object other) { if (other == this) { return true; } - return (other instanceof MockitoOverrideMetadata that && super.equals(that) && + return (other instanceof AbstractMockitoOverrideMetadata that && super.equals(that) && (this.reset == that.reset) && (this.proxyTargetAware == that.proxyTargetAware)); } diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockReset.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockReset.java index d8d8cbed0ffc..3a12bc92ff01 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockReset.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockReset.java @@ -16,8 +16,6 @@ package org.springframework.test.context.bean.override.mockito; -import java.util.List; - import org.mockito.MockSettings; import org.mockito.MockingDetails; import org.mockito.Mockito; @@ -28,11 +26,15 @@ import org.springframework.util.Assert; /** - * Reset strategy used on a mock bean. Usually applied to a mock through the - * {@link MockitoBean @MockitoBean} annotation but can also be directly applied - * to any mock in the {@code ApplicationContext} using the static methods. + * Reset strategy used on a mock bean. + * + *

Usually applied to a mock via the {@link MockitoBean @MockitoBean} or + * {@link MockitoSpyBean @MockitoSpyBean} annotation but can also be directly + * applied to any mock in the {@code ApplicationContext} using the static methods + * in this class. * * @author Phillip Webb + * @author Sam Brannen * @since 6.2 * @see MockitoResetTestExecutionListener */ @@ -49,7 +51,7 @@ public enum MockReset { AFTER, /** - * Don't reset the mock. + * Do not reset the mock. */ NONE; @@ -102,39 +104,26 @@ public static MockSettings apply(MockReset reset, MockSettings settings) { * @return the reset type (never {@code null}) */ static MockReset get(Object mock) { - MockReset reset = MockReset.NONE; MockingDetails mockingDetails = Mockito.mockingDetails(mock); if (mockingDetails.isMock()) { MockCreationSettings settings = mockingDetails.getMockCreationSettings(); - List listeners = settings.getInvocationListeners(); - for (Object listener : listeners) { + for (InvocationListener listener : settings.getInvocationListeners()) { if (listener instanceof ResetInvocationListener resetInvocationListener) { - reset = resetInvocationListener.getReset(); + return resetInvocationListener.reset; } } } - return reset; + return MockReset.NONE; } /** * Dummy {@link InvocationListener} used to hold the {@link MockReset} value. */ - private static class ResetInvocationListener implements InvocationListener { - - private final MockReset reset; - - ResetInvocationListener(MockReset reset) { - this.reset = reset; - } - - MockReset getReset() { - return this.reset; - } + private record ResetInvocationListener(MockReset reset) implements InvocationListener { @Override public void reportInvocation(MethodInvocationReport methodInvocationReport) { } - } } diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBean.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBean.java index d865e559a6cc..9d0737392fb3 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBean.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBean.java @@ -28,10 +28,13 @@ import org.springframework.test.context.bean.override.BeanOverride; /** - * Mark a field to trigger a bean override using a Mockito mock. + * {@code @MockitoBean} is an annotation that can be applied to a field in a test + * class to override a bean in the test's + * {@link org.springframework.context.ApplicationContext ApplicationContext} + * using a Mockito mock. * *

If no explicit {@link #name()} is specified, a target bean definition is - * selected according to the class of the annotated field, and there must be + * selected according to the type of the annotated field, and there must be * exactly one such candidate definition in the context. A {@code @Qualifier} * annotation can be used to help disambiguate. * If a {@link #name()} is specified, either the definition exists in the @@ -40,13 +43,14 @@ * *

Dependencies that are known to the application context but are not beans * (such as those - * {@link org.springframework.beans.factory.config.ConfigurableListableBeanFactory#registerResolvableDependency(Class, Object) + * {@linkplain org.springframework.beans.factory.config.ConfigurableListableBeanFactory#registerResolvableDependency(Class, Object) * registered directly}) will not be found, and a mocked bean will be added to * the context alongside the existing dependency. * * @author Simon Baslé * @since 6.2 - * @see MockitoSpyBean + * @see org.springframework.test.context.bean.override.mockito.MockitoSpyBean @MockitoSpyBean + * @see org.springframework.test.context.bean.override.convention.TestBean @TestBean */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @@ -63,7 +67,7 @@ String name() default ""; /** - * Extra interfaces that should also be declared on the mock. + * Extra interfaces that should also be declared by the mock. *

Defaults to none. * @return any extra interfaces * @see MockSettings#extraInterfaces(Class...) @@ -71,7 +75,7 @@ Class[] extraInterfaces() default {}; /** - * The {@link Answers} type to use on the mock. + * The {@link Answers} type to use in the mock. *

Defaults to {@link Answers#RETURNS_DEFAULTS}. * @return the answer type */ diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideMetadata.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideMetadata.java index cbb4505427b9..765c114d89a1 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideMetadata.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideMetadata.java @@ -45,7 +45,7 @@ * @author Sam Brannen * @since 6.2 */ -class MockitoBeanOverrideMetadata extends MockitoOverrideMetadata { +class MockitoBeanOverrideMetadata extends AbstractMockitoOverrideMetadata { private final Set> extraInterfaces; diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessor.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessor.java index febb7538b3bc..16c16411ba9b 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessor.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessor.java @@ -23,27 +23,28 @@ import org.springframework.test.context.bean.override.BeanOverrideProcessor; /** - * {@link BeanOverrideProcessor} implementation for Mockito support. Both mocking - * and spying are supported. + * {@link BeanOverrideProcessor} implementation that provides support for + * {@link MockitoBean @MockitoBean} and {@link MockitoSpyBean @MockitoSpyBean}. * * @author Simon Baslé * @since 6.2 - * @see MockitoBean - * @see MockitoSpyBean + * @see MockitoBean @MockitoBean + * @see MockitoSpyBean @MockitoSpyBean */ class MockitoBeanOverrideProcessor implements BeanOverrideProcessor { @Override - public MockitoOverrideMetadata createMetadata(Annotation overrideAnnotation, Class testClass, Field field) { + public AbstractMockitoOverrideMetadata createMetadata(Annotation overrideAnnotation, Class testClass, Field field) { if (overrideAnnotation instanceof MockitoBean mockBean) { return new MockitoBeanOverrideMetadata(field, ResolvableType.forField(field, testClass), mockBean); } else if (overrideAnnotation instanceof MockitoSpyBean spyBean) { return new MockitoSpyBeanOverrideMetadata(field, ResolvableType.forField(field, testClass), spyBean); } - throw new IllegalStateException(String.format("Invalid annotation passed to MockitoBeanOverrideProcessor: " - + "expected @MockitoBean/@MockitoSpyBean on field %s.%s", - field.getDeclaringClass().getName(), field.getName())); + throw new IllegalStateException(""" + Invalid annotation passed to MockitoBeanOverrideProcessor: \ + expected either @MockitoBean or @MockitoSpyBean on field %s.%s""" + .formatted(field.getDeclaringClass().getName(), field.getName())); } } 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 index 33f0f5f8a253..c375af2b653a 100644 --- 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 @@ -25,8 +25,9 @@ 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. + * Configure a test class that uses {@link MockitoBean @MockitoBean} or + * {@link MockitoSpyBean @MockitoSpyBean} to set up Mockito with an explicit + * stubbing strictness mode. * * @author Simon Baslé * @since 6.2 @@ -38,8 +39,8 @@ public @interface MockitoBeanSettings { /** - * The stubbing strictness to apply for all Mockito mocks in the annotated - * class. + * The stubbing strictness mode to apply for all Mockito mocks in the annotated + * test class. */ Strictness value(); 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 bbf8f8182033..46cf654c1858 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 @@ -40,8 +40,11 @@ * with a {@link MockReset}. * * @author Phillip Webb + * @author Sam Brannen * @since 6.2 * @see MockitoTestExecutionListener + * @see MockitoBean @MockitoBean + * @see MockitoSpyBean @MockitoSpyBean */ public class MockitoResetTestExecutionListener extends AbstractTestExecutionListener { @@ -75,13 +78,13 @@ private void resetMocks(ApplicationContext applicationContext, MockReset reset) private void resetMocks(ConfigurableApplicationContext applicationContext, MockReset reset) { ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory(); - String[] names = beanFactory.getBeanDefinitionNames(); + String[] beanNames = beanFactory.getBeanDefinitionNames(); Set instantiatedSingletons = new HashSet<>(Arrays.asList(beanFactory.getSingletonNames())); - for (String name : names) { - BeanDefinition definition = beanFactory.getBeanDefinition(name); - if (definition.isSingleton() && instantiatedSingletons.contains(name)) { - Object bean = getBean(beanFactory, name); - if (bean != null && reset.equals(MockReset.get(bean))) { + for (String beanName : beanNames) { + BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); + if (beanDefinition.isSingleton() && instantiatedSingletons.contains(beanName)) { + Object bean = getBean(beanFactory, beanName); + if (bean != null && reset == MockReset.get(bean)) { Mockito.reset(bean); } } @@ -98,20 +101,20 @@ private void resetMocks(ConfigurableApplicationContext applicationContext, MockR } @Nullable - private Object getBean(ConfigurableListableBeanFactory beanFactory, String name) { + private Object getBean(ConfigurableListableBeanFactory beanFactory, String beanName) { try { - if (isStandardBeanOrSingletonFactoryBean(beanFactory, name)) { - return beanFactory.getBean(name); + if (isStandardBeanOrSingletonFactoryBean(beanFactory, beanName)) { + return beanFactory.getBean(beanName); } } catch (Exception ex) { // Continue } - return beanFactory.getSingleton(name); + return beanFactory.getSingleton(beanName); } - private boolean isStandardBeanOrSingletonFactoryBean(ConfigurableListableBeanFactory beanFactory, String name) { - String factoryBeanName = BeanFactory.FACTORY_BEAN_PREFIX + name; + private boolean isStandardBeanOrSingletonFactoryBean(ConfigurableListableBeanFactory beanFactory, String beanName) { + String factoryBeanName = BeanFactory.FACTORY_BEAN_PREFIX + beanName; if (beanFactory.containsBean(factoryBeanName)) { FactoryBean factoryBean = (FactoryBean) beanFactory.getBean(factoryBeanName); return factoryBean.isSingleton(); diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBean.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBean.java index 1192cec86685..db2b8fb3ce45 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBean.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBean.java @@ -44,7 +44,8 @@ * * @author Simon Baslé * @since 6.2 - * @see MockitoBean + * @see org.springframework.test.context.bean.override.mockito.MockitoBean @MockitoBean + * @see org.springframework.test.context.bean.override.convention.TestBean @TestBean */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideMetadata.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideMetadata.java index 4a96d585743c..ed6234c8f8a9 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideMetadata.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideMetadata.java @@ -42,7 +42,7 @@ * @author Stephane Nicoll * @since 6.2 */ -class MockitoSpyBeanOverrideMetadata extends MockitoOverrideMetadata { +class MockitoSpyBeanOverrideMetadata extends AbstractMockitoOverrideMetadata { MockitoSpyBeanOverrideMetadata(Field field, ResolvableType typeToSpy, MockitoSpyBean spyAnnotation) { this(field, typeToSpy, (StringUtils.hasText(spyAnnotation.name()) ? spyAnnotation.name() : null), @@ -62,17 +62,17 @@ protected Object createOverride(String beanName, @Nullable BeanDefinition existi @Nullable Object existingBeanInstance) { Assert.notNull(existingBeanInstance, - () -> "MockitoSpyBean requires an existing bean instance for bean " + beanName); + () -> "@MockitoSpyBean requires an existing bean instance for bean " + beanName); return createSpy(beanName, existingBeanInstance); } @SuppressWarnings("unchecked") - private T createSpy(String name, Object instance) { + private Object createSpy(String name, Object instance) { Class resolvedTypeToOverride = getBeanType().resolve(); Assert.notNull(resolvedTypeToOverride, "Failed to resolve type to override"); Assert.isInstanceOf(resolvedTypeToOverride, instance); if (Mockito.mockingDetails(instance).isSpy()) { - return (T) instance; + return instance; } MockSettings settings = MockReset.withSettings(getReset()); if (StringUtils.hasLength(name)) { @@ -91,7 +91,7 @@ private T createSpy(String name, Object instance) { settings.spiedInstance(instance); toSpy = instance.getClass(); } - return (T) Mockito.mock(toSpy, settings); + return Mockito.mock(toSpy, settings); } 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 3eca29fad200..aca1a6aaff11 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 @@ -17,9 +17,11 @@ package org.springframework.test.context.bean.override.mockito; import java.lang.annotation.Annotation; -import java.lang.reflect.Field; -import java.util.LinkedHashSet; +import java.lang.reflect.AnnotatedElement; +import java.util.Arrays; +import java.util.HashSet; import java.util.Set; +import java.util.function.Predicate; import org.mockito.Mockito; import org.mockito.MockitoSession; @@ -31,7 +33,6 @@ import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; -import org.springframework.util.ReflectionUtils.FieldCallback; /** * {@code TestExecutionListener} that enables {@link MockitoBean @MockitoBean} @@ -41,7 +42,7 @@ * *

The {@link MockitoSession#setStrictness(Strictness) strictness} of the * session defaults to {@link Strictness#STRICT_STUBS}. Use - * {@link MockitoBeanSettings} to specify a different strictness. + * {@link MockitoBeanSettings @MockitoBeanSettings} to specify a different strictness. * *

The automatic reset support for {@code @MockBean} and {@code @SpyBean} is * handled by the {@link MockitoResetTestExecutionListener}. @@ -50,8 +51,11 @@ * @author Phillip Webb * @author Andy Wilkinson * @author Moritz Halbritter + * @author Sam Brannen * @since 6.2 * @see MockitoResetTestExecutionListener + * @see MockitoBean @MockitoBean + * @see MockitoSpyBean @MockitoSpyBean */ public class MockitoTestExecutionListener extends AbstractTestExecutionListener { @@ -101,7 +105,7 @@ public void afterTestClass(TestContext testContext) throws Exception { } private void initMocks(TestContext testContext) { - if (hasMockitoAnnotations(testContext)) { + if (MockitoAnnotationDetector.hasMockitoAnnotations(testContext.getTestClass())) { Object testInstance = testContext.getTestInstance(); MockitoBeanSettings annotation = AnnotationUtils.findAnnotation(testInstance.getClass(), MockitoBeanSettings.class); @@ -124,51 +128,35 @@ private void closeMocks(TestContext testContext) throws Exception { } } - private boolean hasMockitoAnnotations(TestContext testContext) { - MockitoAnnotationCollector collector = new MockitoAnnotationCollector(); - collector.collect(testContext.getTestClass()); - return collector.hasAnnotations(); - } - /** - * Utility class that collects {@code org.mockito} annotations and the - * annotations in this package (like {@link MockitoBeanSettings}). + * Utility class that detects {@code org.mockito} annotations as well as the + * annotations in this package (like {@link MockitoBeanSettings @MockitoBeanSettings}). */ - private static final class MockitoAnnotationCollector implements FieldCallback { + private static class MockitoAnnotationDetector { - private static final String MOCKITO_BEAN_PACKAGE = MockitoBean.class.getPackageName(); + private static final String MOCKITO_BEAN_PACKAGE = MockitoBeanSettings.class.getPackageName(); private static final String ORG_MOCKITO_PACKAGE = "org.mockito"; - private final Set annotations = new LinkedHashSet<>(); - - public void collect(Class clazz) { - ReflectionUtils.doWithFields(clazz, this); - for (Annotation annotation : clazz.getAnnotations()) { - collect(annotation); - } - } - - @Override - public void doWith(Field field) throws IllegalArgumentException { - for (Annotation annotation : field.getAnnotations()) { - collect(annotation); - } - } - - private void collect(Annotation annotation) { - String packageName = annotation.annotationType().getPackageName(); - if (packageName.startsWith(MOCKITO_BEAN_PACKAGE) || - packageName.startsWith(ORG_MOCKITO_PACKAGE)) { - this.annotations.add(annotation); - } + 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) { + Set annotations = new HashSet<>(); + collect(testClass, annotations); + ReflectionUtils.doWithFields(testClass, field -> collect(field, annotations)); + return !annotations.isEmpty(); } - boolean hasAnnotations() { - return !this.annotations.isEmpty(); + static void collect(AnnotatedElement annotatedElement, Set annotations) { + Arrays.stream(annotatedElement.getAnnotations()) + .filter(isMockitoAnnotation) + .forEach(annotations::add); } - } } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/easymock/EasyMockBeanOverrideMetadata.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/easymock/EasyMockBeanOverrideMetadata.java index accbcb6aa8fc..96b9e3c13d77 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/easymock/EasyMockBeanOverrideMetadata.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/easymock/EasyMockBeanOverrideMetadata.java @@ -21,7 +21,6 @@ import org.easymock.EasyMock; import org.easymock.MockType; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.SingletonBeanRegistry; import org.springframework.core.ResolvableType; @@ -62,17 +61,15 @@ protected void track(Object mock, SingletonBeanRegistry singletonBeanRegistry) { getEasyMockBeans(singletonBeanRegistry).add(mock); } - private EasyMockBeans getEasyMockBeans(SingletonBeanRegistry singletonBeanRegistry) { - String className = EasyMockBeans.class.getName(); + private static EasyMockBeans getEasyMockBeans(SingletonBeanRegistry singletonBeanRegistry) { + String beanName = EasyMockBeans.class.getName(); EasyMockBeans easyMockBeans = null; - try { - easyMockBeans = (EasyMockBeans) singletonBeanRegistry.getSingleton(className); - } - catch (NoSuchBeanDefinitionException ignored) { + if (singletonBeanRegistry.containsSingleton(beanName)) { + easyMockBeans = (EasyMockBeans) singletonBeanRegistry.getSingleton(beanName); } if (easyMockBeans == null) { easyMockBeans = new EasyMockBeans(); - singletonBeanRegistry.registerSingleton(className, easyMockBeans); + singletonBeanRegistry.registerSingleton(beanName, easyMockBeans); } return easyMockBeans; } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/easymock/EasyMockBeanOverrideProcessor.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/easymock/EasyMockBeanOverrideProcessor.java index e6a7a09f408a..954c77d0e9b2 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/easymock/EasyMockBeanOverrideProcessor.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/easymock/EasyMockBeanOverrideProcessor.java @@ -34,7 +34,7 @@ class EasyMockBeanOverrideProcessor implements BeanOverrideProcessor { @Override public OverrideMetadata createMetadata(Annotation annotation, Class testClass, Field field) { EasyMockBean easyMockBean = (EasyMockBean) annotation; - String beanName = (StringUtils.hasText(easyMockBean.name()) ? easyMockBean.name() : field.getName()); + String beanName = (StringUtils.hasText(easyMockBean.name()) ? easyMockBean.name() : null); return new EasyMockBeanOverrideMetadata(field, field.getType(), beanName, easyMockBean.mockType()); } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/easymock/EasyMockResetTestExecutionListener.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/easymock/EasyMockResetTestExecutionListener.java index bb5d46bfbb50..723f9ae04d60 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/easymock/EasyMockResetTestExecutionListener.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/easymock/EasyMockResetTestExecutionListener.java @@ -16,6 +16,7 @@ package org.springframework.test.context.bean.override.easymock; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; @@ -55,7 +56,15 @@ private void resetMocks(ApplicationContext applicationContext) { private void resetMocks(ConfigurableApplicationContext applicationContext) { ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory(); - beanFactory.getBean(EasyMockBeans.class).resetAll(); + try { + beanFactory.getBean(EasyMockBeans.class).resetAll(); + } + catch (NoSuchBeanDefinitionException ex) { + // Continue + } + if (applicationContext.getParent() != null) { + resetMocks(applicationContext.getParent()); + } } } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessorTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessorTests.java index eed7c1a8443d..19feda9f2f65 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessorTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessorTests.java @@ -64,8 +64,8 @@ void otherAnnotationThrows() throws NoSuchFieldException { assertThatIllegalStateException() .isThrownBy(() -> this.processor.createMetadata(annotation, clazz, field)) - .withMessage("Invalid annotation passed to MockitoBeanOverrideProcessor: expected " + - "@MockitoBean/@MockitoSpyBean on field %s.%s", field.getDeclaringClass().getName(), + .withMessage("Invalid annotation passed to MockitoBeanOverrideProcessor: expected either " + + "@MockitoBean or @MockitoSpyBean on field %s.%s", field.getDeclaringClass().getName(), field.getName()); }