Browse Source

Revise Bean Override internals and Javadoc

pull/33607/head
Sam Brannen 1 year ago
parent
commit
40ca83dfd4
  1. 11
      spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverride.java
  2. 8
      spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java
  3. 4
      spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideProcessor.java
  4. 5
      spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideStrategy.java
  5. 22
      spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBean.java
  6. 30
      spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/AbstractMockitoOverrideMetadata.java
  7. 35
      spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockReset.java
  8. 16
      spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBean.java
  9. 2
      spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideMetadata.java
  10. 17
      spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessor.java
  11. 9
      spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanSettings.java
  12. 27
      spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoResetTestExecutionListener.java
  13. 3
      spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBean.java
  14. 10
      spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideMetadata.java
  15. 68
      spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoTestExecutionListener.java
  16. 13
      spring-test/src/test/java/org/springframework/test/context/bean/override/easymock/EasyMockBeanOverrideMetadata.java
  17. 2
      spring-test/src/test/java/org/springframework/test/context/bean/override/easymock/EasyMockBeanOverrideProcessor.java
  18. 11
      spring-test/src/test/java/org/springframework/test/context/bean/override/easymock/EasyMockResetTestExecutionListener.java
  19. 4
      spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessorTests.java

11
spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverride.java

@ -16,6 +16,7 @@
package org.springframework.test.context.bean.override; package org.springframework.test.context.bean.override;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
@ -24,17 +25,23 @@ import java.lang.annotation.Target;
/** /**
* Mark a composed annotation as eligible for Bean Override processing. * Mark a composed annotation as eligible for Bean Override processing.
* *
* <p>Specifying this annotation triggers the configured {@link BeanOverrideProcessor} * <p>Specifying this annotation registers the configured {@link BeanOverrideProcessor}
* which must be capable of handling the composed annotation and its attributes. * which must be capable of handling the composed annotation and its attributes.
* *
* <p>Since the composed annotation should only be applied to fields, it is * <p>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)}.
*
* <p>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é * @author Simon Baslé
* @since 6.2 * @since 6.2
*/ */
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE) @Target(ElementType.ANNOTATION_TYPE)
@Documented
public @interface BeanOverride { public @interface BeanOverride {
/** /**

8
spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java

@ -63,12 +63,12 @@ import org.springframework.util.StringUtils;
*/ */
class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered { class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {
private static final BeanNameGenerator beanNameGenerator = DefaultBeanNameGenerator.INSTANCE;
private final Set<OverrideMetadata> metadata; private final Set<OverrideMetadata> metadata;
private final BeanOverrideRegistrar overrideRegistrar; private final BeanOverrideRegistrar overrideRegistrar;
private final BeanNameGenerator beanNameGenerator = new DefaultBeanNameGenerator();
/** /**
* Create a new {@code BeanOverrideBeanFactoryPostProcessor} with the supplied * Create a new {@code BeanOverrideBeanFactoryPostProcessor} with the supplied
@ -215,7 +215,7 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
"Unable to override bean: no bean definitions of type %s (as required by annotated field '%s.%s')" "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())); .formatted(overrideMetadata.getBeanType(), field.getDeclaringClass().getSimpleName(), field.getName()));
} }
return this.beanNameGenerator.generateBeanName(beanDefinition, registry); return beanNameGenerator.generateBeanName(beanDefinition, registry);
} }
Field field = overrideMetadata.getField(); Field field = overrideMetadata.getField();
@ -230,7 +230,7 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
boolean checkAutowiredCandidate) { boolean checkAutowiredCandidate) {
ResolvableType resolvableType = metadata.getBeanType(); ResolvableType resolvableType = metadata.getBeanType();
Class<?> type = resolvableType.resolve(Object.class); Class<?> type = resolvableType.toClass();
// Start with matching bean names for type, excluding FactoryBeans. // Start with matching bean names for type, excluding FactoryBeans.
Set<String> beanNames = new LinkedHashSet<>( Set<String> beanNames = new LinkedHashSet<>(

4
spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideProcessor.java

@ -26,7 +26,7 @@ import java.lang.reflect.Field;
* <p>At least one composed annotation that is meta-annotated with * <p>At least one composed annotation that is meta-annotated with
* {@link BeanOverride @BeanOverride} must be a companion of this processor and * {@link BeanOverride @BeanOverride} must be a companion of this processor and
* may provide additional user settings that drive how the concrete * may provide additional user settings that drive how the concrete
* {@link OverrideMetadata} is configured. * {@code OverrideMetadata} is configured.
* *
* <p>Implementations are required to have a no-argument constructor and be * <p>Implementations are required to have a no-argument constructor and be
* stateless. * stateless.
@ -41,7 +41,7 @@ public interface BeanOverrideProcessor {
/** /**
* Create an {@link OverrideMetadata} instance for the given annotated field. * Create an {@link OverrideMetadata} instance for the given annotated field.
* @param overrideAnnotation the composed annotation that declares the * @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 testClass the test class to process
* @param field the annotated field * @param field the annotated field
* @return the {@link OverrideMetadata} instance that should handle the * @return the {@link OverrideMetadata} instance that should handle the

5
spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideStrategy.java

@ -21,14 +21,15 @@ package org.springframework.test.context.bean.override;
* *
* @author Simon Baslé * @author Simon Baslé
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Sam Brannen
* @since 6.2 * @since 6.2
*/ */
public enum BeanOverrideStrategy { public enum BeanOverrideStrategy {
/** /**
* Replace a given bean definition, immediately preparing a singleton instance. * Replace a given bean definition, immediately preparing a singleton instance.
* <p>Fails if the original bean definition exists. To create a new bean * <p>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}. * definition in such a case, use {@link #REPLACE_OR_CREATE_DEFINITION} instead.
*/ */
REPLACE_DEFINITION, REPLACE_DEFINITION,

22
spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBean.java

@ -26,7 +26,10 @@ import org.springframework.core.annotation.AliasFor;
import org.springframework.test.context.bean.override.BeanOverride; 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.
* *
* <p>By default, the bean to override is inferred from the type of the * <p>By default, the bean to override is inferred from the type of the
* annotated field. This requires that exactly one matching bean definition is * annotated field. This requires that exactly one matching bean definition is
@ -57,13 +60,12 @@ import org.springframework.test.context.bean.override.BeanOverride;
* *
* <p>Consider the following example. * <p>Consider the following example.
* *
* <pre><code> * <pre><code> class CustomerServiceTests {
* class CustomerServiceTests {
* *
* &#064;TestBean * &#064;TestBean
* private CustomerRepository repository; * private CustomerRepository repository;
* *
* // Tests * // &#064;Test methods ...
* *
* private static CustomerRepository repository() { * private static CustomerRepository repository() {
* return new TestCustomerRepository(); * return new TestCustomerRepository();
@ -79,15 +81,14 @@ import org.springframework.test.context.bean.override.BeanOverride;
* <p>To make things more explicit, the bean and method names can be set, * <p>To make things more explicit, the bean and method names can be set,
* as shown in the following example. * as shown in the following example.
* *
* <pre><code> * <pre><code> class CustomerServiceTests {
* class CustomerServiceTests {
* *
* &#064;TestBean(name = "customerRepository", methodName = "createTestCustomerRepository") * &#064;TestBean(name = "customerRepository", methodName = "createTestCustomerRepository")
* private CustomerRepository repository; * CustomerRepository repository;
* *
* // Tests * // &#064;Test methods ...
* *
* private static CustomerRepository createTestCustomerRepository() { * static CustomerRepository createTestCustomerRepository() {
* return new TestCustomerRepository(); * return new TestCustomerRepository();
* } * }
* }</code></pre> * }</code></pre>
@ -96,7 +97,8 @@ import org.springframework.test.context.bean.override.BeanOverride;
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Sam Brannen * @author Sam Brannen
* @since 6.2 * @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) @Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)

30
spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoOverrideMetadata.java → spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/AbstractMockitoOverrideMetadata.java

@ -19,7 +19,6 @@ package org.springframework.test.context.bean.override.mockito;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Objects; import java.util.Objects;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.SingletonBeanRegistry; import org.springframework.beans.factory.config.SingletonBeanRegistry;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.core.style.ToStringCreator; import org.springframework.core.style.ToStringCreator;
@ -28,21 +27,21 @@ import org.springframework.test.context.bean.override.BeanOverrideStrategy;
import org.springframework.test.context.bean.override.OverrideMetadata; 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 Phillip Webb
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Sam Brannen * @author Sam Brannen
* @since 6.2 * @since 6.2
*/ */
abstract class MockitoOverrideMetadata extends OverrideMetadata { abstract class AbstractMockitoOverrideMetadata extends OverrideMetadata {
private final MockReset reset; private final MockReset reset;
private final boolean proxyTargetAware; 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) { BeanOverrideStrategy strategy, @Nullable MockReset reset, boolean proxyTargetAware) {
super(field, beanType, beanName, strategy); super(field, beanType, beanName, strategy);
@ -69,17 +68,20 @@ abstract class MockitoOverrideMetadata extends OverrideMetadata {
@Override @Override
protected void track(Object mock, SingletonBeanRegistry trackingBeanRegistry) { protected void track(Object mock, SingletonBeanRegistry trackingBeanRegistry) {
MockitoBeans tracker = null; getMockitoBeans(trackingBeanRegistry).add(mock);
try { }
tracker = (MockitoBeans) trackingBeanRegistry.getSingleton(MockitoBeans.class.getName());
} private static MockitoBeans getMockitoBeans(SingletonBeanRegistry trackingBeanRegistry) {
catch (NoSuchBeanDefinitionException ignored) { String beanName = MockitoBeans.class.getName();
MockitoBeans mockitoBeans = null;
if (trackingBeanRegistry.containsSingleton(beanName)) {
mockitoBeans = (MockitoBeans) trackingBeanRegistry.getSingleton(beanName);
} }
if (tracker == null) { if (mockitoBeans == null) {
tracker = new MockitoBeans(); mockitoBeans = new MockitoBeans();
trackingBeanRegistry.registerSingleton(MockitoBeans.class.getName(), tracker); trackingBeanRegistry.registerSingleton(beanName, mockitoBeans);
} }
tracker.add(mock); return mockitoBeans;
} }
@Override @Override
@ -87,7 +89,7 @@ abstract class MockitoOverrideMetadata extends OverrideMetadata {
if (other == this) { if (other == this) {
return true; 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)); (this.reset == that.reset) && (this.proxyTargetAware == that.proxyTargetAware));
} }

35
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; package org.springframework.test.context.bean.override.mockito;
import java.util.List;
import org.mockito.MockSettings; import org.mockito.MockSettings;
import org.mockito.MockingDetails; import org.mockito.MockingDetails;
import org.mockito.Mockito; import org.mockito.Mockito;
@ -28,11 +26,15 @@ import org.mockito.mock.MockCreationSettings;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
* Reset strategy used on a mock bean. Usually applied to a mock through the * Reset strategy used on a mock bean.
* {@link MockitoBean @MockitoBean} annotation but can also be directly applied *
* to any mock in the {@code ApplicationContext} using the static methods. * <p>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 Phillip Webb
* @author Sam Brannen
* @since 6.2 * @since 6.2
* @see MockitoResetTestExecutionListener * @see MockitoResetTestExecutionListener
*/ */
@ -49,7 +51,7 @@ public enum MockReset {
AFTER, AFTER,
/** /**
* Don't reset the mock. * Do not reset the mock.
*/ */
NONE; NONE;
@ -102,39 +104,26 @@ public enum MockReset {
* @return the reset type (never {@code null}) * @return the reset type (never {@code null})
*/ */
static MockReset get(Object mock) { static MockReset get(Object mock) {
MockReset reset = MockReset.NONE;
MockingDetails mockingDetails = Mockito.mockingDetails(mock); MockingDetails mockingDetails = Mockito.mockingDetails(mock);
if (mockingDetails.isMock()) { if (mockingDetails.isMock()) {
MockCreationSettings<?> settings = mockingDetails.getMockCreationSettings(); MockCreationSettings<?> settings = mockingDetails.getMockCreationSettings();
List<InvocationListener> listeners = settings.getInvocationListeners(); for (InvocationListener listener : settings.getInvocationListeners()) {
for (Object listener : listeners) {
if (listener instanceof ResetInvocationListener resetInvocationListener) { 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. * Dummy {@link InvocationListener} used to hold the {@link MockReset} value.
*/ */
private static class ResetInvocationListener implements InvocationListener { private record ResetInvocationListener(MockReset reset) implements InvocationListener {
private final MockReset reset;
ResetInvocationListener(MockReset reset) {
this.reset = reset;
}
MockReset getReset() {
return this.reset;
}
@Override @Override
public void reportInvocation(MethodInvocationReport methodInvocationReport) { public void reportInvocation(MethodInvocationReport methodInvocationReport) {
} }
} }
} }

16
spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBean.java

@ -28,10 +28,13 @@ import org.mockito.MockSettings;
import org.springframework.test.context.bean.override.BeanOverride; 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.
* *
* <p>If no explicit {@link #name()} is specified, a target bean definition is * <p>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} * exactly one such candidate definition in the context. A {@code @Qualifier}
* annotation can be used to help disambiguate. * annotation can be used to help disambiguate.
* If a {@link #name()} is specified, either the definition exists in the * If a {@link #name()} is specified, either the definition exists in the
@ -40,13 +43,14 @@ import org.springframework.test.context.bean.override.BeanOverride;
* *
* <p>Dependencies that are known to the application context but are not beans * <p>Dependencies that are known to the application context but are not beans
* (such as those * (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 * registered directly}) will not be found, and a mocked bean will be added to
* the context alongside the existing dependency. * the context alongside the existing dependency.
* *
* @author Simon Baslé * @author Simon Baslé
* @since 6.2 * @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) @Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@ -63,7 +67,7 @@ public @interface MockitoBean {
String name() default ""; String name() default "";
/** /**
* Extra interfaces that should also be declared on the mock. * Extra interfaces that should also be declared by the mock.
* <p>Defaults to none. * <p>Defaults to none.
* @return any extra interfaces * @return any extra interfaces
* @see MockSettings#extraInterfaces(Class...) * @see MockSettings#extraInterfaces(Class...)
@ -71,7 +75,7 @@ public @interface MockitoBean {
Class<?>[] extraInterfaces() default {}; Class<?>[] extraInterfaces() default {};
/** /**
* The {@link Answers} type to use on the mock. * The {@link Answers} type to use in the mock.
* <p>Defaults to {@link Answers#RETURNS_DEFAULTS}. * <p>Defaults to {@link Answers#RETURNS_DEFAULTS}.
* @return the answer type * @return the answer type
*/ */

2
spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideMetadata.java

@ -45,7 +45,7 @@ import org.springframework.util.StringUtils;
* @author Sam Brannen * @author Sam Brannen
* @since 6.2 * @since 6.2
*/ */
class MockitoBeanOverrideMetadata extends MockitoOverrideMetadata { class MockitoBeanOverrideMetadata extends AbstractMockitoOverrideMetadata {
private final Set<Class<?>> extraInterfaces; private final Set<Class<?>> extraInterfaces;

17
spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessor.java

@ -23,27 +23,28 @@ import org.springframework.core.ResolvableType;
import org.springframework.test.context.bean.override.BeanOverrideProcessor; import org.springframework.test.context.bean.override.BeanOverrideProcessor;
/** /**
* {@link BeanOverrideProcessor} implementation for Mockito support. Both mocking * {@link BeanOverrideProcessor} implementation that provides support for
* and spying are supported. * {@link MockitoBean @MockitoBean} and {@link MockitoSpyBean @MockitoSpyBean}.
* *
* @author Simon Baslé * @author Simon Baslé
* @since 6.2 * @since 6.2
* @see MockitoBean * @see MockitoBean @MockitoBean
* @see MockitoSpyBean * @see MockitoSpyBean @MockitoSpyBean
*/ */
class MockitoBeanOverrideProcessor implements BeanOverrideProcessor { class MockitoBeanOverrideProcessor implements BeanOverrideProcessor {
@Override @Override
public MockitoOverrideMetadata createMetadata(Annotation overrideAnnotation, Class<?> testClass, Field field) { public AbstractMockitoOverrideMetadata createMetadata(Annotation overrideAnnotation, Class<?> testClass, Field field) {
if (overrideAnnotation instanceof MockitoBean mockBean) { if (overrideAnnotation instanceof MockitoBean mockBean) {
return new MockitoBeanOverrideMetadata(field, ResolvableType.forField(field, testClass), mockBean); return new MockitoBeanOverrideMetadata(field, ResolvableType.forField(field, testClass), mockBean);
} }
else if (overrideAnnotation instanceof MockitoSpyBean spyBean) { else if (overrideAnnotation instanceof MockitoSpyBean spyBean) {
return new MockitoSpyBeanOverrideMetadata(field, ResolvableType.forField(field, testClass), spyBean); return new MockitoSpyBeanOverrideMetadata(field, ResolvableType.forField(field, testClass), spyBean);
} }
throw new IllegalStateException(String.format("Invalid annotation passed to MockitoBeanOverrideProcessor: " throw new IllegalStateException("""
+ "expected @MockitoBean/@MockitoSpyBean on field %s.%s", Invalid annotation passed to MockitoBeanOverrideProcessor: \
field.getDeclaringClass().getName(), field.getName())); expected either @MockitoBean or @MockitoSpyBean on field %s.%s"""
.formatted(field.getDeclaringClass().getName(), field.getName()));
} }
} }

9
spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanSettings.java

@ -25,8 +25,9 @@ import java.lang.annotation.Target;
import org.mockito.quality.Strictness; import org.mockito.quality.Strictness;
/** /**
* Configure a test class that uses {@link MockitoBean} or {@link MockitoSpyBean} * Configure a test class that uses {@link MockitoBean @MockitoBean} or
* to set up Mockito with an explicitly specified stubbing strictness. * {@link MockitoSpyBean @MockitoSpyBean} to set up Mockito with an explicit
* stubbing strictness mode.
* *
* @author Simon Baslé * @author Simon Baslé
* @since 6.2 * @since 6.2
@ -38,8 +39,8 @@ import org.mockito.quality.Strictness;
public @interface MockitoBeanSettings { public @interface MockitoBeanSettings {
/** /**
* The stubbing strictness to apply for all Mockito mocks in the annotated * The stubbing strictness mode to apply for all Mockito mocks in the annotated
* class. * test class.
*/ */
Strictness value(); Strictness value();

27
spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoResetTestExecutionListener.java

@ -40,8 +40,11 @@ import org.springframework.test.context.support.AbstractTestExecutionListener;
* with a {@link MockReset}. * with a {@link MockReset}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Sam Brannen
* @since 6.2 * @since 6.2
* @see MockitoTestExecutionListener * @see MockitoTestExecutionListener
* @see MockitoBean @MockitoBean
* @see MockitoSpyBean @MockitoSpyBean
*/ */
public class MockitoResetTestExecutionListener extends AbstractTestExecutionListener { public class MockitoResetTestExecutionListener extends AbstractTestExecutionListener {
@ -75,13 +78,13 @@ public class MockitoResetTestExecutionListener extends AbstractTestExecutionList
private void resetMocks(ConfigurableApplicationContext applicationContext, MockReset reset) { private void resetMocks(ConfigurableApplicationContext applicationContext, MockReset reset) {
ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory(); ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
String[] names = beanFactory.getBeanDefinitionNames(); String[] beanNames = beanFactory.getBeanDefinitionNames();
Set<String> instantiatedSingletons = new HashSet<>(Arrays.asList(beanFactory.getSingletonNames())); Set<String> instantiatedSingletons = new HashSet<>(Arrays.asList(beanFactory.getSingletonNames()));
for (String name : names) { for (String beanName : beanNames) {
BeanDefinition definition = beanFactory.getBeanDefinition(name); BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
if (definition.isSingleton() && instantiatedSingletons.contains(name)) { if (beanDefinition.isSingleton() && instantiatedSingletons.contains(beanName)) {
Object bean = getBean(beanFactory, name); Object bean = getBean(beanFactory, beanName);
if (bean != null && reset.equals(MockReset.get(bean))) { if (bean != null && reset == MockReset.get(bean)) {
Mockito.reset(bean); Mockito.reset(bean);
} }
} }
@ -98,20 +101,20 @@ public class MockitoResetTestExecutionListener extends AbstractTestExecutionList
} }
@Nullable @Nullable
private Object getBean(ConfigurableListableBeanFactory beanFactory, String name) { private Object getBean(ConfigurableListableBeanFactory beanFactory, String beanName) {
try { try {
if (isStandardBeanOrSingletonFactoryBean(beanFactory, name)) { if (isStandardBeanOrSingletonFactoryBean(beanFactory, beanName)) {
return beanFactory.getBean(name); return beanFactory.getBean(beanName);
} }
} }
catch (Exception ex) { catch (Exception ex) {
// Continue // Continue
} }
return beanFactory.getSingleton(name); return beanFactory.getSingleton(beanName);
} }
private boolean isStandardBeanOrSingletonFactoryBean(ConfigurableListableBeanFactory beanFactory, String name) { private boolean isStandardBeanOrSingletonFactoryBean(ConfigurableListableBeanFactory beanFactory, String beanName) {
String factoryBeanName = BeanFactory.FACTORY_BEAN_PREFIX + name; String factoryBeanName = BeanFactory.FACTORY_BEAN_PREFIX + beanName;
if (beanFactory.containsBean(factoryBeanName)) { if (beanFactory.containsBean(factoryBeanName)) {
FactoryBean<?> factoryBean = (FactoryBean<?>) beanFactory.getBean(factoryBeanName); FactoryBean<?> factoryBean = (FactoryBean<?>) beanFactory.getBean(factoryBeanName);
return factoryBean.isSingleton(); return factoryBean.isSingleton();

3
spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBean.java

@ -44,7 +44,8 @@ import org.springframework.test.context.bean.override.BeanOverride;
* *
* @author Simon Baslé * @author Simon Baslé
* @since 6.2 * @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) @Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)

10
spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideMetadata.java

@ -42,7 +42,7 @@ import org.springframework.util.StringUtils;
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 6.2 * @since 6.2
*/ */
class MockitoSpyBeanOverrideMetadata extends MockitoOverrideMetadata { class MockitoSpyBeanOverrideMetadata extends AbstractMockitoOverrideMetadata {
MockitoSpyBeanOverrideMetadata(Field field, ResolvableType typeToSpy, MockitoSpyBean spyAnnotation) { MockitoSpyBeanOverrideMetadata(Field field, ResolvableType typeToSpy, MockitoSpyBean spyAnnotation) {
this(field, typeToSpy, (StringUtils.hasText(spyAnnotation.name()) ? spyAnnotation.name() : null), this(field, typeToSpy, (StringUtils.hasText(spyAnnotation.name()) ? spyAnnotation.name() : null),
@ -62,17 +62,17 @@ class MockitoSpyBeanOverrideMetadata extends MockitoOverrideMetadata {
@Nullable Object existingBeanInstance) { @Nullable Object existingBeanInstance) {
Assert.notNull(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); return createSpy(beanName, existingBeanInstance);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <T> T createSpy(String name, Object instance) { private Object createSpy(String name, Object instance) {
Class<?> resolvedTypeToOverride = getBeanType().resolve(); Class<?> resolvedTypeToOverride = getBeanType().resolve();
Assert.notNull(resolvedTypeToOverride, "Failed to resolve type to override"); Assert.notNull(resolvedTypeToOverride, "Failed to resolve type to override");
Assert.isInstanceOf(resolvedTypeToOverride, instance); Assert.isInstanceOf(resolvedTypeToOverride, instance);
if (Mockito.mockingDetails(instance).isSpy()) { if (Mockito.mockingDetails(instance).isSpy()) {
return (T) instance; return instance;
} }
MockSettings settings = MockReset.withSettings(getReset()); MockSettings settings = MockReset.withSettings(getReset());
if (StringUtils.hasLength(name)) { if (StringUtils.hasLength(name)) {
@ -91,7 +91,7 @@ class MockitoSpyBeanOverrideMetadata extends MockitoOverrideMetadata {
settings.spiedInstance(instance); settings.spiedInstance(instance);
toSpy = instance.getClass(); toSpy = instance.getClass();
} }
return (T) Mockito.mock(toSpy, settings); return Mockito.mock(toSpy, settings);
} }

68
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; package org.springframework.test.context.bean.override.mockito;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Field; import java.lang.reflect.AnnotatedElement;
import java.util.LinkedHashSet; import java.util.Arrays;
import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.MockitoSession; import org.mockito.MockitoSession;
@ -31,7 +33,6 @@ import org.springframework.test.context.support.AbstractTestExecutionListener;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;
/** /**
* {@code TestExecutionListener} that enables {@link MockitoBean @MockitoBean} * {@code TestExecutionListener} that enables {@link MockitoBean @MockitoBean}
@ -41,7 +42,7 @@ import org.springframework.util.ReflectionUtils.FieldCallback;
* *
* <p>The {@link MockitoSession#setStrictness(Strictness) strictness} of the * <p>The {@link MockitoSession#setStrictness(Strictness) strictness} of the
* session defaults to {@link Strictness#STRICT_STUBS}. Use * session defaults to {@link Strictness#STRICT_STUBS}. Use
* {@link MockitoBeanSettings} to specify a different strictness. * {@link MockitoBeanSettings @MockitoBeanSettings} to specify a different strictness.
* *
* <p>The automatic reset support for {@code @MockBean} and {@code @SpyBean} is * <p>The automatic reset support for {@code @MockBean} and {@code @SpyBean} is
* handled by the {@link MockitoResetTestExecutionListener}. * handled by the {@link MockitoResetTestExecutionListener}.
@ -50,8 +51,11 @@ import org.springframework.util.ReflectionUtils.FieldCallback;
* @author Phillip Webb * @author Phillip Webb
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Moritz Halbritter * @author Moritz Halbritter
* @author Sam Brannen
* @since 6.2 * @since 6.2
* @see MockitoResetTestExecutionListener * @see MockitoResetTestExecutionListener
* @see MockitoBean @MockitoBean
* @see MockitoSpyBean @MockitoSpyBean
*/ */
public class MockitoTestExecutionListener extends AbstractTestExecutionListener { public class MockitoTestExecutionListener extends AbstractTestExecutionListener {
@ -101,7 +105,7 @@ public class MockitoTestExecutionListener extends AbstractTestExecutionListener
} }
private void initMocks(TestContext testContext) { private void initMocks(TestContext testContext) {
if (hasMockitoAnnotations(testContext)) { if (MockitoAnnotationDetector.hasMockitoAnnotations(testContext.getTestClass())) {
Object testInstance = testContext.getTestInstance(); Object testInstance = testContext.getTestInstance();
MockitoBeanSettings annotation = AnnotationUtils.findAnnotation(testInstance.getClass(), MockitoBeanSettings annotation = AnnotationUtils.findAnnotation(testInstance.getClass(),
MockitoBeanSettings.class); MockitoBeanSettings.class);
@ -124,51 +128,35 @@ public class MockitoTestExecutionListener extends AbstractTestExecutionListener
} }
} }
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 * Utility class that detects {@code org.mockito} annotations as well as the
* annotations in this package (like {@link MockitoBeanSettings}). * 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 static final String ORG_MOCKITO_PACKAGE = "org.mockito";
private final Set<Annotation> annotations = new LinkedHashSet<>(); private static final Predicate<Annotation> isMockitoAnnotation = annotation -> {
String packageName = annotation.annotationType().getPackageName();
public void collect(Class<?> clazz) { return (packageName.startsWith(MOCKITO_BEAN_PACKAGE) ||
ReflectionUtils.doWithFields(clazz, this); packageName.startsWith(ORG_MOCKITO_PACKAGE));
for (Annotation annotation : clazz.getAnnotations()) { };
collect(annotation);
} static boolean hasMockitoAnnotations(Class<?> testClass) {
} Set<Annotation> annotations = new HashSet<>();
collect(testClass, annotations);
@Override ReflectionUtils.doWithFields(testClass, field -> collect(field, annotations));
public void doWith(Field field) throws IllegalArgumentException { return !annotations.isEmpty();
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);
}
} }
boolean hasAnnotations() { static void collect(AnnotatedElement annotatedElement, Set<Annotation> annotations) {
return !this.annotations.isEmpty(); Arrays.stream(annotatedElement.getAnnotations())
.filter(isMockitoAnnotation)
.forEach(annotations::add);
} }
} }
} }

13
spring-test/src/test/java/org/springframework/test/context/bean/override/easymock/EasyMockBeanOverrideMetadata.java

@ -21,7 +21,6 @@ import java.lang.reflect.Field;
import org.easymock.EasyMock; import org.easymock.EasyMock;
import org.easymock.MockType; import org.easymock.MockType;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.SingletonBeanRegistry; import org.springframework.beans.factory.config.SingletonBeanRegistry;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
@ -62,17 +61,15 @@ class EasyMockBeanOverrideMetadata extends OverrideMetadata {
getEasyMockBeans(singletonBeanRegistry).add(mock); getEasyMockBeans(singletonBeanRegistry).add(mock);
} }
private EasyMockBeans getEasyMockBeans(SingletonBeanRegistry singletonBeanRegistry) { private static EasyMockBeans getEasyMockBeans(SingletonBeanRegistry singletonBeanRegistry) {
String className = EasyMockBeans.class.getName(); String beanName = EasyMockBeans.class.getName();
EasyMockBeans easyMockBeans = null; EasyMockBeans easyMockBeans = null;
try { if (singletonBeanRegistry.containsSingleton(beanName)) {
easyMockBeans = (EasyMockBeans) singletonBeanRegistry.getSingleton(className); easyMockBeans = (EasyMockBeans) singletonBeanRegistry.getSingleton(beanName);
}
catch (NoSuchBeanDefinitionException ignored) {
} }
if (easyMockBeans == null) { if (easyMockBeans == null) {
easyMockBeans = new EasyMockBeans(); easyMockBeans = new EasyMockBeans();
singletonBeanRegistry.registerSingleton(className, easyMockBeans); singletonBeanRegistry.registerSingleton(beanName, easyMockBeans);
} }
return easyMockBeans; return easyMockBeans;
} }

2
spring-test/src/test/java/org/springframework/test/context/bean/override/easymock/EasyMockBeanOverrideProcessor.java

@ -34,7 +34,7 @@ class EasyMockBeanOverrideProcessor implements BeanOverrideProcessor {
@Override @Override
public OverrideMetadata createMetadata(Annotation annotation, Class<?> testClass, Field field) { public OverrideMetadata createMetadata(Annotation annotation, Class<?> testClass, Field field) {
EasyMockBean easyMockBean = (EasyMockBean) annotation; 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()); return new EasyMockBeanOverrideMetadata(field, field.getType(), beanName, easyMockBean.mockType());
} }

11
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; package org.springframework.test.context.bean.override.easymock;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
@ -55,7 +56,15 @@ class EasyMockResetTestExecutionListener extends AbstractTestExecutionListener {
private void resetMocks(ConfigurableApplicationContext applicationContext) { private void resetMocks(ConfigurableApplicationContext applicationContext) {
ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory(); 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());
}
} }
} }

4
spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideProcessorTests.java

@ -64,8 +64,8 @@ public class MockitoBeanOverrideProcessorTests {
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(() -> this.processor.createMetadata(annotation, clazz, field)) .isThrownBy(() -> this.processor.createMetadata(annotation, clazz, field))
.withMessage("Invalid annotation passed to MockitoBeanOverrideProcessor: expected " + .withMessage("Invalid annotation passed to MockitoBeanOverrideProcessor: expected either " +
"@MockitoBean/@MockitoSpyBean on field %s.%s", field.getDeclaringClass().getName(), "@MockitoBean or @MockitoSpyBean on field %s.%s", field.getDeclaringClass().getName(),
field.getName()); field.getName());
} }

Loading…
Cancel
Save