Polish Bean Override support in the TestContext framework
This commit is contained in:
@@ -23,24 +23,27 @@ import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Mark an annotation as eligible for Bean Override parsing.
|
||||
* This meta-annotation provides a {@link BeanOverrideProcessor} class which
|
||||
* must be capable of handling the annotated annotation.
|
||||
*
|
||||
* <p>Target annotation must have a {@link RetentionPolicy} of {@code RUNTIME}
|
||||
* and be applicable to {@link java.lang.reflect.Field Fields} only.
|
||||
* @see BeanOverrideBeanPostProcessor
|
||||
* <p>This meta-annotation specifies a {@link BeanOverrideProcessor} class which
|
||||
* must be capable of handling the composed annotation that is meta-annotated
|
||||
* with {@code @BeanOverride}.
|
||||
*
|
||||
* <p>The composed annotation that is meta-annotated with {@code @BeanOverride}
|
||||
* must have a {@code RetentionPolicy} of {@link RetentionPolicy#RUNTIME RUNTIME}
|
||||
* and a {@code Target} of {@link ElementType#FIELD FIELD}.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @since 6.2
|
||||
* @see BeanOverrideBeanPostProcessor
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.ANNOTATION_TYPE})
|
||||
@Target(ElementType.ANNOTATION_TYPE)
|
||||
public @interface BeanOverride {
|
||||
|
||||
/**
|
||||
* A {@link BeanOverrideProcessor} implementation class by which the target
|
||||
* annotation should be processed. Implementations must have a no-argument
|
||||
* constructor.
|
||||
* A {@link BeanOverrideProcessor} implementation class by which the composed
|
||||
* annotation should be processed.
|
||||
*/
|
||||
Class<? extends BeanOverrideProcessor> value();
|
||||
|
||||
}
|
||||
|
||||
@@ -53,19 +53,20 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@link BeanFactoryPostProcessor} used to register and inject overriding
|
||||
* bean metadata with the {@link ApplicationContext}. A set of
|
||||
* {@link OverrideMetadata} must be passed to the processor.
|
||||
* A {@link BeanOverrideParser} can typically be used to parse these from test
|
||||
* classes that use any annotation meta-annotated with {@link BeanOverride} to
|
||||
* mark override sites.
|
||||
* bean metadata with the {@link ApplicationContext}.
|
||||
*
|
||||
* <p>This processor supports two {@link BeanOverrideStrategy}:
|
||||
* <p>A set of {@link OverrideMetadata} must be provided to this processor. A
|
||||
* {@link BeanOverrideParser} can typically be used to parse this metadata from
|
||||
* test classes that use any annotation meta-annotated with
|
||||
* {@link BeanOverride @BeanOverride} to mark override sites.
|
||||
*
|
||||
* <p>This processor supports two types of {@link BeanOverrideStrategy}:
|
||||
* <ul>
|
||||
* <li>replacing a given bean's definition, immediately preparing a singleton
|
||||
* <li>Replacing a given bean's definition, immediately preparing a singleton
|
||||
* instance</li>
|
||||
* <li>intercepting the actual bean instance upon creation and wrapping it,
|
||||
* <li>Intercepting the actual bean instance upon creation and wrapping it,
|
||||
* using the early bean definition mechanism of
|
||||
* {@link SmartInstantiationAwareBeanPostProcessor}).</li>
|
||||
* {@link SmartInstantiationAwareBeanPostProcessor}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>This processor also provides support for injecting the overridden bean
|
||||
@@ -78,19 +79,25 @@ public class BeanOverrideBeanPostProcessor implements InstantiationAwareBeanPost
|
||||
BeanFactoryAware, BeanFactoryPostProcessor, Ordered {
|
||||
|
||||
private static final String INFRASTRUCTURE_BEAN_NAME = BeanOverrideBeanPostProcessor.class.getName();
|
||||
private static final String EARLY_INFRASTRUCTURE_BEAN_NAME = BeanOverrideBeanPostProcessor.WrapEarlyBeanPostProcessor.class.getName();
|
||||
|
||||
private final Set<OverrideMetadata> overrideMetadata;
|
||||
private static final String EARLY_INFRASTRUCTURE_BEAN_NAME =
|
||||
BeanOverrideBeanPostProcessor.WrapEarlyBeanPostProcessor.class.getName();
|
||||
|
||||
|
||||
private final Map<String, OverrideMetadata> earlyOverrideMetadata = new HashMap<>();
|
||||
|
||||
private ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
private final Map<OverrideMetadata, String> beanNameRegistry = new HashMap<>();
|
||||
|
||||
private final Map<Field, String> fieldRegistry = new HashMap<>();
|
||||
|
||||
private final Set<OverrideMetadata> overrideMetadata;
|
||||
|
||||
@Nullable
|
||||
private ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link BeanOverrideBeanPostProcessor} instance with the
|
||||
* Create a new {@code BeanOverrideBeanPostProcessor} instance with the
|
||||
* given {@link OverrideMetadata} set.
|
||||
* @param overrideMetadata the initial override metadata
|
||||
*/
|
||||
@@ -107,7 +114,7 @@ public class BeanOverrideBeanPostProcessor implements InstantiationAwareBeanPost
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory,
|
||||
"Beans overriding can only be used with a ConfigurableListableBeanFactory");
|
||||
"Bean overriding can only be used with a ConfigurableListableBeanFactory");
|
||||
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
|
||||
}
|
||||
|
||||
@@ -120,7 +127,7 @@ public class BeanOverrideBeanPostProcessor implements InstantiationAwareBeanPost
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
Assert.state(this.beanFactory == beanFactory, "Unexpected beanFactory to postProcess");
|
||||
Assert.state(this.beanFactory == beanFactory, "Unexpected BeanFactory to post-process");
|
||||
Assert.isInstanceOf(BeanDefinitionRegistry.class, beanFactory,
|
||||
"Bean overriding annotations can only be used on bean factories that implement "
|
||||
+ "BeanDefinitionRegistry");
|
||||
@@ -128,17 +135,17 @@ public class BeanOverrideBeanPostProcessor implements InstantiationAwareBeanPost
|
||||
}
|
||||
|
||||
private void postProcessWithRegistry(BeanDefinitionRegistry registry) {
|
||||
//Note that a tracker bean is registered down the line only if there is some overrideMetadata parsed
|
||||
Set<OverrideMetadata> overrideMetadata = getOverrideMetadata();
|
||||
for (OverrideMetadata metadata : overrideMetadata) {
|
||||
// Note that a tracker bean is registered down the line only if there is some overrideMetadata parsed.
|
||||
for (OverrideMetadata metadata : getOverrideMetadata()) {
|
||||
registerBeanOverride(registry, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the details of a {@link BeanDefinition} to the definition created by
|
||||
* this processor for a given {@link OverrideMetadata}. Defaults to copying
|
||||
* the {@link BeanDefinition#isPrimary()} attribute and scope.
|
||||
* Copy certain details of a {@link BeanDefinition} to the definition created by
|
||||
* this processor for a given {@link OverrideMetadata}.
|
||||
* <p>The default implementation copies the {@linkplain BeanDefinition#isPrimary()
|
||||
* primary flag} and the {@linkplain BeanDefinition#getScope() scope}.
|
||||
*/
|
||||
protected void copyBeanDefinitionDetails(BeanDefinition from, RootBeanDefinition to) {
|
||||
to.setPrimary(from.isPrimary());
|
||||
@@ -155,6 +162,7 @@ public class BeanOverrideBeanPostProcessor implements InstantiationAwareBeanPost
|
||||
|
||||
private void registerReplaceDefinition(BeanDefinitionRegistry registry, OverrideMetadata overrideMetadata,
|
||||
boolean enforceExistingDefinition) {
|
||||
|
||||
RootBeanDefinition beanDefinition = createBeanDefinition(overrideMetadata);
|
||||
String beanName = overrideMetadata.getExpectedBeanName();
|
||||
|
||||
@@ -166,7 +174,7 @@ public class BeanOverrideBeanPostProcessor implements InstantiationAwareBeanPost
|
||||
}
|
||||
else if (enforceExistingDefinition) {
|
||||
throw new IllegalStateException("Unable to override " + overrideMetadata.getBeanOverrideDescription() +
|
||||
" bean, expected a bean definition to replace with name '" + beanName + "'");
|
||||
" bean; expected a bean definition to replace with name '" + beanName + "'");
|
||||
}
|
||||
registry.registerBeanDefinition(beanName, beanDefinition);
|
||||
|
||||
@@ -185,10 +193,10 @@ public class BeanOverrideBeanPostProcessor implements InstantiationAwareBeanPost
|
||||
|
||||
/**
|
||||
* Check that the expected bean name is registered and matches the type to override.
|
||||
* If so, put the override metadata in the early tracking map.
|
||||
* The map will later be checked to see if a given bean should be wrapped
|
||||
* <p>If so, put the override metadata in the early tracking map.
|
||||
* <p>The map will later be checked to see if a given bean should be wrapped
|
||||
* upon creation, during the {@link WrapEarlyBeanPostProcessor#getEarlyBeanReference(Object, String)}
|
||||
* phase
|
||||
* phase.
|
||||
*/
|
||||
private void registerWrapEarly(OverrideMetadata metadata) {
|
||||
Set<String> existingBeanNames = getExistingBeanNames(metadata.typeToOverride());
|
||||
@@ -203,11 +211,12 @@ public class BeanOverrideBeanPostProcessor implements InstantiationAwareBeanPost
|
||||
}
|
||||
|
||||
/**
|
||||
* Check early overrides records and use the {@link OverrideMetadata} to
|
||||
* Check early override records and use the {@link OverrideMetadata} to
|
||||
* create an override instance from the provided bean, if relevant.
|
||||
* <p>Called during the {@link SmartInstantiationAwareBeanPostProcessor}
|
||||
* phases (see {@link WrapEarlyBeanPostProcessor#getEarlyBeanReference(Object, String)}
|
||||
* and {@link WrapEarlyBeanPostProcessor#postProcessAfterInitialization(Object, String)}).
|
||||
* phases.
|
||||
* @see WrapEarlyBeanPostProcessor#getEarlyBeanReference(Object, String)
|
||||
* @see WrapEarlyBeanPostProcessor#postProcessAfterInitialization(Object, String)
|
||||
*/
|
||||
protected final Object wrapIfNecessary(Object bean, String beanName) throws BeansException {
|
||||
final OverrideMetadata metadata = this.earlyOverrideMetadata.get(beanName);
|
||||
@@ -236,26 +245,10 @@ public class BeanOverrideBeanPostProcessor implements InstantiationAwareBeanPost
|
||||
beans.add(beanName);
|
||||
}
|
||||
}
|
||||
beans.removeIf(this::isScopedTarget);
|
||||
beans.removeIf(ScopedProxyUtils::isScopedTarget);
|
||||
return beans;
|
||||
}
|
||||
|
||||
private boolean isScopedTarget(String beanName) {
|
||||
try {
|
||||
return ScopedProxyUtils.isScopedTarget(beanName);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void postProcessField(Object bean, Field field) {
|
||||
String beanName = this.fieldRegistry.get(field);
|
||||
if (StringUtils.hasText(beanName)) {
|
||||
inject(field, bean, beanName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
|
||||
throws BeansException {
|
||||
@@ -263,9 +256,17 @@ public class BeanOverrideBeanPostProcessor implements InstantiationAwareBeanPost
|
||||
return pvs;
|
||||
}
|
||||
|
||||
private void postProcessField(Object bean, Field field) {
|
||||
String beanName = this.fieldRegistry.get(field);
|
||||
if (StringUtils.hasText(beanName)) {
|
||||
inject(field, bean, beanName);
|
||||
}
|
||||
}
|
||||
|
||||
void inject(Field field, Object target, OverrideMetadata overrideMetadata) {
|
||||
String beanName = this.beanNameRegistry.get(overrideMetadata);
|
||||
Assert.state(StringUtils.hasLength(beanName), () -> "No bean found for overrideMetadata " + overrideMetadata);
|
||||
Assert.state(StringUtils.hasLength(beanName),
|
||||
() -> "No bean found for OverrideMetadata: " + overrideMetadata);
|
||||
inject(field, target, beanName);
|
||||
}
|
||||
|
||||
@@ -287,25 +288,26 @@ public class BeanOverrideBeanPostProcessor implements InstantiationAwareBeanPost
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the processor with a {@link BeanDefinitionRegistry}.
|
||||
* Not required when using the Spring TestContext Framework, as registration
|
||||
* is automatic via the {@link org.springframework.core.io.support.SpringFactoriesLoader SpringFactoriesLoader}
|
||||
* Register a {@link BeanOverrideBeanPostProcessor} with a {@link BeanDefinitionRegistry}.
|
||||
* <p>Not required when using the Spring TestContext Framework, as registration
|
||||
* is automatic via the
|
||||
* {@link org.springframework.core.io.support.SpringFactoriesLoader SpringFactoriesLoader}
|
||||
* mechanism.
|
||||
* @param registry the bean definition registry
|
||||
* @param overrideMetadata the initial override metadata set
|
||||
*/
|
||||
public static void register(BeanDefinitionRegistry registry, @Nullable Set<OverrideMetadata> overrideMetadata) {
|
||||
//early processor
|
||||
getOrAddInfrastructureBeanDefinition(registry, WrapEarlyBeanPostProcessor.class, EARLY_INFRASTRUCTURE_BEAN_NAME,
|
||||
constructorArguments -> constructorArguments.addIndexedArgumentValue(0,
|
||||
new RuntimeBeanReference(INFRASTRUCTURE_BEAN_NAME)));
|
||||
// Early processor
|
||||
getOrAddInfrastructureBeanDefinition(
|
||||
registry, WrapEarlyBeanPostProcessor.class, EARLY_INFRASTRUCTURE_BEAN_NAME, constructorArgs ->
|
||||
constructorArgs.addIndexedArgumentValue(0, new RuntimeBeanReference(INFRASTRUCTURE_BEAN_NAME)));
|
||||
|
||||
//main processor
|
||||
BeanDefinition definition = getOrAddInfrastructureBeanDefinition(registry, BeanOverrideBeanPostProcessor.class,
|
||||
INFRASTRUCTURE_BEAN_NAME, constructorArguments -> constructorArguments
|
||||
.addIndexedArgumentValue(0, new LinkedHashSet<OverrideMetadata>()));
|
||||
ConstructorArgumentValues.ValueHolder constructorArg = definition.getConstructorArgumentValues()
|
||||
.getIndexedArgumentValue(0, Set.class);
|
||||
// Main processor
|
||||
BeanDefinition definition = getOrAddInfrastructureBeanDefinition(
|
||||
registry, BeanOverrideBeanPostProcessor.class, INFRASTRUCTURE_BEAN_NAME, constructorArgs ->
|
||||
constructorArgs.addIndexedArgumentValue(0, new LinkedHashSet<OverrideMetadata>()));
|
||||
ConstructorArgumentValues.ValueHolder constructorArg =
|
||||
definition.getConstructorArgumentValues().getIndexedArgumentValue(0, Set.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<OverrideMetadata> existing = (Set<OverrideMetadata>) constructorArg.getValue();
|
||||
if (overrideMetadata != null && existing != null) {
|
||||
@@ -315,6 +317,7 @@ public class BeanOverrideBeanPostProcessor implements InstantiationAwareBeanPost
|
||||
|
||||
private static BeanDefinition getOrAddInfrastructureBeanDefinition(BeanDefinitionRegistry registry,
|
||||
Class<?> clazz, String beanName, Consumer<ConstructorArgumentValues> constructorArgumentsConsumer) {
|
||||
|
||||
if (!registry.containsBeanDefinition(beanName)) {
|
||||
RootBeanDefinition definition = new RootBeanDefinition(clazz);
|
||||
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
@@ -326,17 +329,21 @@ public class BeanOverrideBeanPostProcessor implements InstantiationAwareBeanPost
|
||||
return registry.getBeanDefinition(beanName);
|
||||
}
|
||||
|
||||
|
||||
private static final class WrapEarlyBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor,
|
||||
PriorityOrdered {
|
||||
|
||||
private final BeanOverrideBeanPostProcessor mainProcessor;
|
||||
|
||||
private final Map<String, Object> earlyReferences;
|
||||
|
||||
|
||||
private WrapEarlyBeanPostProcessor(BeanOverrideBeanPostProcessor mainProcessor) {
|
||||
this.mainProcessor = mainProcessor;
|
||||
this.earlyReferences = new ConcurrentHashMap<>(16);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE;
|
||||
@@ -363,8 +370,9 @@ public class BeanOverrideBeanPostProcessor implements InstantiationAwareBeanPost
|
||||
}
|
||||
|
||||
private String getCacheKey(Object bean, String beanName) {
|
||||
return StringUtils.hasLength(beanName) ? beanName : bean.getClass().getName();
|
||||
return (StringUtils.hasLength(beanName) ? beanName : bean.getClass().getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ package org.springframework.test.bean.override;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.aot.hint.annotation.Reflective;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.test.context.ContextConfigurationAttributes;
|
||||
@@ -29,7 +28,8 @@ import org.springframework.test.context.MergedContextConfiguration;
|
||||
import org.springframework.test.context.TestContextAnnotationUtils;
|
||||
|
||||
/**
|
||||
* A {@link ContextCustomizerFactory} to add support for Bean Overriding.
|
||||
* {@link ContextCustomizerFactory} which provides support for Bean Overriding
|
||||
* in tests.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @since 6.2
|
||||
@@ -39,6 +39,7 @@ public class BeanOverrideContextCustomizerFactory implements ContextCustomizerFa
|
||||
@Override
|
||||
public ContextCustomizer createContextCustomizer(Class<?> testClass,
|
||||
List<ContextConfigurationAttributes> configAttributes) {
|
||||
|
||||
BeanOverrideParser parser = new BeanOverrideParser();
|
||||
parseMetadata(testClass, parser);
|
||||
if (parser.getOverrideMetadata().isEmpty()) {
|
||||
@@ -56,10 +57,9 @@ public class BeanOverrideContextCustomizerFactory implements ContextCustomizerFa
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link ContextCustomizer} for Bean Overriding in tests.
|
||||
* {@link ContextCustomizer} for Bean Overriding in tests.
|
||||
*/
|
||||
@Reflective
|
||||
static final class BeanOverrideContextCustomizer implements ContextCustomizer {
|
||||
private static final class BeanOverrideContextCustomizer implements ContextCustomizer {
|
||||
|
||||
private final Set<OverrideMetadata> metadata;
|
||||
|
||||
@@ -97,4 +97,5 @@ public class BeanOverrideContextCustomizerFactory implements ContextCustomizerFa
|
||||
return this.metadata.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,23 +34,22 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.springframework.core.annotation.MergedAnnotations.SearchStrategy.DIRECT;
|
||||
|
||||
/**
|
||||
* A parser that discovers annotations meta-annotated with {@link BeanOverride}
|
||||
* A parser that discovers annotations meta-annotated with {@link BeanOverride @BeanOverride}
|
||||
* on fields of a given class and creates {@link OverrideMetadata} accordingly.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @since 6.2
|
||||
*/
|
||||
class BeanOverrideParser {
|
||||
|
||||
private final Set<OverrideMetadata> parsedMetadata;
|
||||
private final Set<OverrideMetadata> parsedMetadata = new LinkedHashSet<>();
|
||||
|
||||
BeanOverrideParser() {
|
||||
this.parsedMetadata = new LinkedHashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the set of {@link OverrideMetadata} once {@link #parse(Class)}
|
||||
* has been called.
|
||||
* Get the set of {@link OverrideMetadata} once {@link #parse(Class)} has been called.
|
||||
*/
|
||||
Set<OverrideMetadata> getOverrideMetadata() {
|
||||
return Collections.unmodifiableSet(this.parsedMetadata);
|
||||
@@ -58,12 +57,12 @@ class BeanOverrideParser {
|
||||
|
||||
/**
|
||||
* Discover fields of the provided class that are meta-annotated with
|
||||
* {@link BeanOverride}, then instantiate their corresponding
|
||||
* {@link BeanOverrideProcessor} and use it to create an {@link OverrideMetadata}
|
||||
* instance for each field. Each call to {@code parse} adds the parsed
|
||||
* metadata to the parser's override metadata {{@link #getOverrideMetadata()}
|
||||
* set}
|
||||
* @param testClass the class which fields to inspect
|
||||
* {@link BeanOverride @BeanOverride}, then instantiate the corresponding
|
||||
* {@link BeanOverrideProcessor} and use it to create {@link OverrideMetadata}
|
||||
* for each field.
|
||||
* <p>Each call to {@code parse} adds the parsed metadata to the parser's
|
||||
* override metadata {@link #getOverrideMetadata() set}.
|
||||
* @param testClass the test class in which to inspect fields
|
||||
*/
|
||||
void parse(Class<?> testClass) {
|
||||
ReflectionUtils.doWithFields(testClass, field -> parseField(field, testClass));
|
||||
@@ -71,11 +70,12 @@ class BeanOverrideParser {
|
||||
|
||||
/**
|
||||
* Check if any field of the provided {@code testClass} is meta-annotated
|
||||
* with {@link BeanOverride}.
|
||||
* with {@link BeanOverride @BeanOverride}.
|
||||
* <p>This is similar to the initial discovery of fields in {@link #parse(Class)}
|
||||
* without the heavier steps of instantiating processors and creating
|
||||
* {@link OverrideMetadata}, so this method leaves the current state of
|
||||
* {@link #getOverrideMetadata()} unchanged.
|
||||
* {@link OverrideMetadata}. Consequently, this method leaves the current
|
||||
* state of the parser's override metadata {@link #getOverrideMetadata() set}
|
||||
* unchanged.
|
||||
* @param testClass the class which fields to inspect
|
||||
* @return true if there is a bean override annotation present, false otherwise
|
||||
* @see #parse(Class)
|
||||
@@ -86,7 +86,7 @@ class BeanOverrideParser {
|
||||
if (hasBeanOverride.get()) {
|
||||
return;
|
||||
}
|
||||
final long count = MergedAnnotations.from(field, MergedAnnotations.SearchStrategy.DIRECT)
|
||||
long count = MergedAnnotations.from(field, DIRECT)
|
||||
.stream(BeanOverride.class)
|
||||
.count();
|
||||
hasBeanOverride.compareAndSet(false, count > 0L);
|
||||
@@ -97,45 +97,46 @@ class BeanOverrideParser {
|
||||
private void parseField(Field field, Class<?> source) {
|
||||
AtomicBoolean overrideAnnotationFound = new AtomicBoolean();
|
||||
|
||||
MergedAnnotations.from(field, MergedAnnotations.SearchStrategy.DIRECT)
|
||||
MergedAnnotations.from(field, DIRECT)
|
||||
.stream(BeanOverride.class)
|
||||
.map(bo -> {
|
||||
MergedAnnotation<?> a = bo.getMetaSource();
|
||||
Assert.notNull(a, "BeanOverride annotation must be meta-present");
|
||||
return new AnnotationPair(a.synthesize(), bo);
|
||||
.map(mergedAnnotation -> {
|
||||
MergedAnnotation<?> metaSource = mergedAnnotation.getMetaSource();
|
||||
Assert.notNull(metaSource, "@BeanOverride annotation must be meta-present");
|
||||
return new AnnotationPair(metaSource.synthesize(), mergedAnnotation);
|
||||
})
|
||||
.forEach(pair -> {
|
||||
BeanOverride metaAnnotation = pair.metaAnnotation().synthesize();
|
||||
final BeanOverrideProcessor processor = getProcessorInstance(metaAnnotation.value());
|
||||
BeanOverride beanOverride = pair.mergedAnnotation().synthesize();
|
||||
BeanOverrideProcessor processor = getProcessorInstance(beanOverride.value());
|
||||
if (processor == null) {
|
||||
return;
|
||||
}
|
||||
ResolvableType typeToOverride = processor.getOrDeduceType(field, pair.annotation(), source);
|
||||
|
||||
Assert.state(overrideAnnotationFound.compareAndSet(false, true),
|
||||
"Multiple bean override annotations found on annotated field <" + field + ">");
|
||||
() -> "Multiple @BeanOverride annotations found on field: " + field);
|
||||
OverrideMetadata metadata = processor.createMetadata(field, pair.annotation(), typeToOverride);
|
||||
boolean isNewDefinition = this.parsedMetadata.add(metadata);
|
||||
Assert.state(isNewDefinition, () -> "Duplicate " + metadata.getBeanOverrideDescription() +
|
||||
" overrideMetadata " + metadata);
|
||||
" OverrideMetadata: " + metadata);
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private BeanOverrideProcessor getProcessorInstance(Class<? extends BeanOverrideProcessor> processorClass) {
|
||||
final Constructor<? extends BeanOverrideProcessor> constructor = ClassUtils.getConstructorIfAvailable(processorClass);
|
||||
Constructor<? extends BeanOverrideProcessor> constructor = ClassUtils.getConstructorIfAvailable(processorClass);
|
||||
if (constructor != null) {
|
||||
ReflectionUtils.makeAccessible(constructor);
|
||||
try {
|
||||
ReflectionUtils.makeAccessible(constructor);
|
||||
return constructor.newInstance();
|
||||
}
|
||||
catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) {
|
||||
throw new BeanDefinitionValidationException("Could not get an instance of BeanOverrideProcessor", ex);
|
||||
throw new BeanDefinitionValidationException(
|
||||
"Failed to instantiate BeanOverrideProcessor of type " + processorClass.getName(), ex);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private record AnnotationPair(Annotation annotation, MergedAnnotation<BeanOverride> metaAnnotation) {}
|
||||
private record AnnotationPair(Annotation annotation, MergedAnnotation<BeanOverride> mergedAnnotation) {}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,14 +22,13 @@ import java.lang.reflect.TypeVariable;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
|
||||
/**
|
||||
* An interface for Bean Overriding concrete processing.
|
||||
* Strategy interface for Bean Override processing.
|
||||
*
|
||||
* <p>Processors are generally linked to one or more specific concrete annotations
|
||||
* (meta-annotated with {@link BeanOverride}) and specify different steps in the
|
||||
* process of parsing these annotations, ultimately creating
|
||||
* (meta-annotated with {@link BeanOverride @BeanOverride}) and specify different
|
||||
* steps in the process of parsing these annotations, ultimately creating
|
||||
* {@link OverrideMetadata} which will be used to instantiate the overrides.
|
||||
*
|
||||
* <p>Implementations are required to have a no-argument constructor and be
|
||||
@@ -42,30 +41,31 @@ import org.springframework.core.annotation.MergedAnnotation;
|
||||
public interface BeanOverrideProcessor {
|
||||
|
||||
/**
|
||||
* Determine a {@link ResolvableType} for which an {@link OverrideMetadata}
|
||||
* instance will be created, e.g. by using the annotation to determine the
|
||||
* type.
|
||||
* <p>Defaults to the field corresponding {@link ResolvableType},
|
||||
* additionally tracking the source class if the field is a {@link TypeVariable}.
|
||||
* Determine the {@link ResolvableType} for which an {@link OverrideMetadata}
|
||||
* instance will be created — for example, by using the supplied annotation
|
||||
* to determine the type.
|
||||
* <p>The default implementation deduces the field's corresponding
|
||||
* {@link ResolvableType}, additionally tracking the source class if the
|
||||
* field's type is a {@link TypeVariable}.
|
||||
*/
|
||||
default ResolvableType getOrDeduceType(Field field, Annotation annotation, Class<?> source) {
|
||||
return (field.getGenericType() instanceof TypeVariable) ? ResolvableType.forField(field, source)
|
||||
: ResolvableType.forField(field);
|
||||
return (field.getGenericType() instanceof TypeVariable ?
|
||||
ResolvableType.forField(field, source) : ResolvableType.forField(field));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link OverrideMetadata} for a given annotated field and target
|
||||
* {@link #getOrDeduceType(Field, Annotation, Class) type}.
|
||||
* Specific implementations of metadata can have state to be used during
|
||||
* override {@link OverrideMetadata#createOverride(String, BeanDefinition,
|
||||
* Object) instance creation}, that is from further parsing the annotation or
|
||||
* the annotated field.
|
||||
* Create an {@link OverrideMetadata} instance for the given annotated field
|
||||
* and target {@link #getOrDeduceType(Field, Annotation, Class) type}.
|
||||
* <p>Specific implementations of metadata can have state to be used during
|
||||
* override {@linkplain OverrideMetadata#createOverride(String, BeanDefinition,
|
||||
* Object) instance creation} — for example, from further parsing of the
|
||||
* annotation or the annotated field.
|
||||
* @param field the annotated field
|
||||
* @param overrideAnnotation the field annotation
|
||||
* @param typeToOverride the target type
|
||||
* @return a new {@link OverrideMetadata}
|
||||
* @return a new {@link OverrideMetadata} instance
|
||||
* @see #getOrDeduceType(Field, Annotation, Class)
|
||||
* @see MergedAnnotation#synthesize()
|
||||
*/
|
||||
OverrideMetadata createMetadata(Field field, Annotation overrideAnnotation, ResolvableType typeToOverride);
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
package org.springframework.test.bean.override;
|
||||
|
||||
/**
|
||||
* Strategies for override instantiation, implemented in
|
||||
* Strategies for bean override instantiation, implemented in
|
||||
* {@link BeanOverrideBeanPostProcessor}.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
@@ -26,22 +26,22 @@ package org.springframework.test.bean.override;
|
||||
public enum BeanOverrideStrategy {
|
||||
|
||||
/**
|
||||
* Replace a given bean definition, immediately preparing a singleton
|
||||
* instance. Enforces the original bean definition to exist.
|
||||
* Replace a given bean definition, immediately preparing a singleton instance.
|
||||
* <p>Requires that the original bean definition exists.
|
||||
*/
|
||||
REPLACE_DEFINITION,
|
||||
|
||||
/**
|
||||
* Replace a given bean definition, immediately preparing a singleton
|
||||
* instance. If the original bean definition does not exist, create the
|
||||
* override definition instead of failing.
|
||||
* Replace a given bean definition, immediately preparing a singleton instance.
|
||||
* <p>If the original bean definition does not exist, an override definition
|
||||
* will be created instead of failing.
|
||||
*/
|
||||
REPLACE_OR_CREATE_DEFINITION,
|
||||
|
||||
/**
|
||||
* Intercept and wrap the actual bean instance upon creation, during
|
||||
* {@link org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference(Object, String)
|
||||
* early bean definition}.
|
||||
* Intercept and wrap the actual bean instance upon creation, during the {@linkplain
|
||||
* org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference(Object, String)
|
||||
* early bean reference} phase.
|
||||
*/
|
||||
WRAP_EARLY_BEAN
|
||||
|
||||
|
||||
@@ -20,17 +20,17 @@ import java.lang.reflect.Field;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.TestExecutionListener;
|
||||
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* A {@link TestExecutionListener} implementation that enables Bean Override
|
||||
* support in tests, injecting overridden beans in appropriate fields.
|
||||
* {@code TestExecutionListener} that enables Bean Override support in tests,
|
||||
* injecting overridden beans in appropriate fields of the test instance.
|
||||
*
|
||||
* <p>Some flavors of Bean Override might additionally require the use of
|
||||
* additional listeners, which should be mentioned in the annotation(s) javadoc.
|
||||
* additional listeners, which should be mentioned in the javadoc for the
|
||||
* corresponding annotations.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @since 6.2
|
||||
@@ -68,36 +68,42 @@ public class BeanOverrideTestExecutionListener extends AbstractTestExecutionList
|
||||
/**
|
||||
* Using a registered {@link BeanOverrideBeanPostProcessor}, find metadata
|
||||
* associated with the current test class and ensure fields are nulled out
|
||||
* then re-injected with the overridden bean instance. This method does
|
||||
* nothing if the {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE}
|
||||
* attribute is not present in the {@code testContext}.
|
||||
* and then re-injected with the overridden bean instance.
|
||||
* <p>This method does nothing if the
|
||||
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE}
|
||||
* attribute is not present in the {@code TestContext}.
|
||||
*/
|
||||
protected void reinjectFieldsIfConfigured(final TestContext testContext) throws Exception {
|
||||
if (Boolean.TRUE.equals(
|
||||
testContext.getAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE))) {
|
||||
|
||||
postProcessFields(testContext, (testMetadata, postProcessor) -> {
|
||||
Field f = testMetadata.overrideMetadata.field();
|
||||
ReflectionUtils.makeAccessible(f);
|
||||
ReflectionUtils.setField(f, testMetadata.testInstance(), null);
|
||||
postProcessor.inject(f, testMetadata.testInstance(), testMetadata.overrideMetadata());
|
||||
Object testInstance = testMetadata.testInstance;
|
||||
Field field = testMetadata.overrideMetadata.field();
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
ReflectionUtils.setField(field, testInstance, null);
|
||||
postProcessor.inject(field, testInstance, testMetadata.overrideMetadata);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void postProcessFields(TestContext testContext, BiConsumer<TestContextOverrideMetadata,
|
||||
BeanOverrideBeanPostProcessor> consumer) {
|
||||
//avoid full parsing but validate that this particular class has some bean override field(s)
|
||||
|
||||
Class<?> testClass = testContext.getTestClass();
|
||||
Object testInstance = testContext.getTestInstance();
|
||||
BeanOverrideParser parser = new BeanOverrideParser();
|
||||
if (parser.hasBeanOverride(testContext.getTestClass())) {
|
||||
BeanOverrideBeanPostProcessor postProcessor = testContext.getApplicationContext()
|
||||
.getBean(BeanOverrideBeanPostProcessor.class);
|
||||
// the class should have already been parsed by the context customizer
|
||||
for (OverrideMetadata metadata: postProcessor.getOverrideMetadata()) {
|
||||
if (!metadata.field().getDeclaringClass().equals(testContext.getTestClass())) {
|
||||
|
||||
// Avoid full parsing, but validate that this particular class has some bean override field(s).
|
||||
if (parser.hasBeanOverride(testClass)) {
|
||||
BeanOverrideBeanPostProcessor postProcessor =
|
||||
testContext.getApplicationContext().getBean(BeanOverrideBeanPostProcessor.class);
|
||||
// The class should have already been parsed by the context customizer.
|
||||
for (OverrideMetadata metadata : postProcessor.getOverrideMetadata()) {
|
||||
if (!metadata.field().getDeclaringClass().equals(testClass)) {
|
||||
continue;
|
||||
}
|
||||
consumer.accept(new TestContextOverrideMetadata(testContext.getTestInstance(), metadata),
|
||||
postProcessor);
|
||||
consumer.accept(new TestContextOverrideMetadata(testInstance, metadata), postProcessor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.Objects;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.SingletonBeanRegistry;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -41,24 +42,27 @@ public abstract class OverrideMetadata {
|
||||
|
||||
private final BeanOverrideStrategy strategy;
|
||||
|
||||
|
||||
protected OverrideMetadata(Field field, Annotation overrideAnnotation,
|
||||
ResolvableType typeToOverride, BeanOverrideStrategy strategy) {
|
||||
|
||||
this.field = field;
|
||||
this.overrideAnnotation = overrideAnnotation;
|
||||
this.typeToOverride = typeToOverride;
|
||||
this.strategy = strategy;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a short human-readable description of the kind of override this
|
||||
* Return a short, human-readable description of the kind of override this
|
||||
* instance handles.
|
||||
*/
|
||||
public abstract String getBeanOverrideDescription();
|
||||
|
||||
/**
|
||||
* Return the expected bean name to override. Typically, this is either
|
||||
* explicitly set in the concrete annotations or defined by the annotated
|
||||
* field's name.
|
||||
* Return the expected bean name to override.
|
||||
* <p>Typically, this is either explicitly set in a concrete annotation or
|
||||
* inferred from the annotated field's name.
|
||||
* @return the expected bean name
|
||||
*/
|
||||
protected String getExpectedBeanName() {
|
||||
@@ -74,7 +78,7 @@ public abstract class OverrideMetadata {
|
||||
|
||||
/**
|
||||
* Return the concrete override annotation, that is the one meta-annotated
|
||||
* with {@link BeanOverride}.
|
||||
* with {@link BeanOverride @BeanOverride}.
|
||||
*/
|
||||
public Annotation overrideAnnotation() {
|
||||
return this.overrideAnnotation;
|
||||
@@ -103,21 +107,21 @@ public abstract class OverrideMetadata {
|
||||
* @param existingBeanDefinition an existing bean definition for that bean
|
||||
* name, or {@code null} if not relevant
|
||||
* @param existingBeanInstance an existing instance for that bean name,
|
||||
* for wrapping purpose, or {@code null} if irrelevant
|
||||
* for wrapping purposes, or {@code null} if irrelevant
|
||||
* @return the instance with which to override the bean
|
||||
*/
|
||||
protected abstract Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition,
|
||||
@Nullable Object existingBeanInstance);
|
||||
|
||||
/**
|
||||
* Optionally track objects created by this {@link OverrideMetadata}
|
||||
* (default is no tracking).
|
||||
* Optionally track objects created by this {@link OverrideMetadata}.
|
||||
* <p>The default is not to track, but this can be overridden in subclasses.
|
||||
* @param override the bean override instance to track
|
||||
* @param trackingBeanRegistry the registry in which trackers could
|
||||
* @param trackingBeanRegistry the registry in which trackers can
|
||||
* optionally be registered
|
||||
*/
|
||||
protected void track(Object override, SingletonBeanRegistry trackingBeanRegistry) {
|
||||
//NO-OP
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -132,21 +136,23 @@ public abstract class OverrideMetadata {
|
||||
return Objects.equals(this.field, that.field) &&
|
||||
Objects.equals(this.overrideAnnotation, that.overrideAnnotation) &&
|
||||
Objects.equals(this.strategy, that.strategy) &&
|
||||
Objects.equals(this.typeToOverride, that.typeToOverride);
|
||||
Objects.equals(typeToOverride(), that.typeToOverride());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.field, this.overrideAnnotation, this.strategy, this.typeToOverride);
|
||||
return Objects.hash(this.field, this.overrideAnnotation, this.strategy, typeToOverride());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OverrideMetadata[" +
|
||||
"category=" + this.getBeanOverrideDescription() + ", " +
|
||||
"field=" + this.field + ", " +
|
||||
"overrideAnnotation=" + this.overrideAnnotation + ", " +
|
||||
"strategy=" + this.strategy + ", " +
|
||||
"typeToOverride=" + this.typeToOverride + ']';
|
||||
return new ToStringCreator(this)
|
||||
.append("category", getBeanOverrideDescription())
|
||||
.append("field", this.field)
|
||||
.append("overrideAnnotation", this.overrideAnnotation)
|
||||
.append("strategy", this.strategy)
|
||||
.append("typeToOverride", typeToOverride())
|
||||
.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-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.
|
||||
@@ -32,10 +32,12 @@ import org.springframework.util.StringUtils;
|
||||
* Base class for {@link MockDefinition} and {@link SpyDefinition}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.2
|
||||
*/
|
||||
abstract class Definition extends OverrideMetadata {
|
||||
|
||||
static final int MULTIPLIER = 31;
|
||||
protected static final int MULTIPLIER = 31;
|
||||
|
||||
|
||||
protected final String name;
|
||||
|
||||
@@ -43,14 +45,17 @@ abstract class Definition extends OverrideMetadata {
|
||||
|
||||
private final boolean proxyTargetAware;
|
||||
|
||||
|
||||
Definition(String name, @Nullable MockReset reset, boolean proxyTargetAware, Field field,
|
||||
Annotation annotation, ResolvableType typeToOverride, BeanOverrideStrategy strategy) {
|
||||
|
||||
super(field, annotation, typeToOverride, strategy);
|
||||
this.name = name;
|
||||
this.reset = (reset != null) ? reset : MockReset.AFTER;
|
||||
this.proxyTargetAware = proxyTargetAware;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected String getExpectedBeanName() {
|
||||
if (StringUtils.hasText(this.name)) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
* Copyright 2012-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.
|
||||
@@ -42,17 +42,17 @@ import static org.mockito.Mockito.mock;
|
||||
* A complete definition that can be used to create a Mockito mock.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.2
|
||||
*/
|
||||
class MockDefinition extends Definition {
|
||||
|
||||
private static final int MULTIPLIER = 31;
|
||||
|
||||
private final Set<Class<?>> extraInterfaces;
|
||||
|
||||
private final Answers answer;
|
||||
|
||||
private final boolean serializable;
|
||||
|
||||
|
||||
MockDefinition(MockitoBean annotation, Field field, ResolvableType typeToMock) {
|
||||
this(annotation.name(), annotation.reset(), field, annotation, typeToMock,
|
||||
annotation.extraInterfaces(), annotation.answers(), annotation.serializable());
|
||||
@@ -60,6 +60,7 @@ class MockDefinition extends Definition {
|
||||
|
||||
MockDefinition(String name, MockReset reset, Field field, Annotation annotation, ResolvableType typeToMock,
|
||||
Class<?>[] extraInterfaces, @Nullable Answers answer, boolean serializable) {
|
||||
|
||||
super(name, reset, false, field, annotation, typeToMock, BeanOverrideStrategy.REPLACE_OR_CREATE_DEFINITION);
|
||||
Assert.notNull(typeToMock, "TypeToMock must not be null");
|
||||
this.extraInterfaces = asClassSet(extraInterfaces);
|
||||
@@ -67,6 +68,7 @@ class MockDefinition extends Definition {
|
||||
this.serializable = serializable;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getBeanOverrideDescription() {
|
||||
return "mock";
|
||||
@@ -119,7 +121,7 @@ class MockDefinition extends Definition {
|
||||
}
|
||||
MockDefinition other = (MockDefinition) obj;
|
||||
boolean result = super.equals(obj);
|
||||
result = result && ObjectUtils.nullSafeEquals(this.typeToOverride(), other.typeToOverride());
|
||||
result = result && ObjectUtils.nullSafeEquals(typeToOverride(), other.typeToOverride());
|
||||
result = result && ObjectUtils.nullSafeEquals(this.extraInterfaces, other.extraInterfaces);
|
||||
result = result && ObjectUtils.nullSafeEquals(this.answer, other.answer);
|
||||
result = result && this.serializable == other.serializable;
|
||||
@@ -129,7 +131,7 @@ class MockDefinition extends Definition {
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.typeToOverride());
|
||||
result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(typeToOverride());
|
||||
result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.extraInterfaces);
|
||||
result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.answer);
|
||||
result = MULTIPLIER * result + Boolean.hashCode(this.serializable);
|
||||
@@ -138,13 +140,14 @@ class MockDefinition extends Definition {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("name", this.name)
|
||||
.append("typeToMock", this.typeToOverride())
|
||||
.append("extraInterfaces", this.extraInterfaces)
|
||||
.append("answer", this.answer)
|
||||
.append("serializable", this.serializable)
|
||||
.append("reset", getReset())
|
||||
.toString();
|
||||
return new ToStringCreator(this)
|
||||
.append("name", this.name)
|
||||
.append("typeToMock", typeToOverride())
|
||||
.append("extraInterfaces", this.extraInterfaces)
|
||||
.append("answer", this.answer)
|
||||
.append("serializable", this.serializable)
|
||||
.append("reset", getReset())
|
||||
.toString();
|
||||
}
|
||||
|
||||
<T> T createMock() {
|
||||
@@ -164,7 +167,7 @@ class MockDefinition extends Definition {
|
||||
if (this.serializable) {
|
||||
settings.serializable();
|
||||
}
|
||||
return (T) mock(this.typeToOverride().resolve(), settings);
|
||||
return (T) mock(typeToOverride().resolve(), settings);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
* Copyright 2012-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.
|
||||
@@ -53,6 +53,7 @@ public enum MockReset {
|
||||
*/
|
||||
NONE;
|
||||
|
||||
|
||||
/**
|
||||
* Create {@link MockSettings settings} to be used with mocks where reset
|
||||
* should occur before each test method runs.
|
||||
|
||||
@@ -34,12 +34,14 @@ import org.springframework.test.bean.override.BeanOverride;
|
||||
* a new one will be added to the context.
|
||||
*
|
||||
* <p>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)
|
||||
* registered directly}) will not be found and a mocked bean will be added to
|
||||
* (such as those
|
||||
* {@link 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
|
||||
*/
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@@ -48,35 +50,38 @@ import org.springframework.test.bean.override.BeanOverride;
|
||||
public @interface MockitoBean {
|
||||
|
||||
/**
|
||||
* The name of the bean to register or replace. If not specified, it will be
|
||||
* the name of the annotated field.
|
||||
* @return the name of the bean
|
||||
* The name of the bean to register or replace.
|
||||
* <p>If not specified, the name of the annotated field will be used.
|
||||
* @return the name of the mocked bean
|
||||
*/
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* Any extra interfaces that should also be declared on the mock.
|
||||
* See {@link MockSettings#extraInterfaces(Class...)} for details.
|
||||
* Extra interfaces that should also be declared on the mock.
|
||||
* <p>Defaults to none.
|
||||
* @return any extra interfaces
|
||||
* @see MockSettings#extraInterfaces(Class...)
|
||||
*/
|
||||
Class<?>[] extraInterfaces() default {};
|
||||
|
||||
/**
|
||||
* The {@link Answers} type to use on the mock.
|
||||
* <p>Defaults to {@link Answers#RETURNS_DEFAULTS}.
|
||||
* @return the answer type
|
||||
*/
|
||||
Answers answers() default Answers.RETURNS_DEFAULTS;
|
||||
|
||||
/**
|
||||
* If the generated mock is serializable.
|
||||
* See {@link MockSettings#serializable()} for details.
|
||||
* @return if the mock is serializable
|
||||
* Whether the generated mock is serializable.
|
||||
* <p>Defaults to {@code false}.
|
||||
* @return {@code true} if the mock is serializable
|
||||
* @see MockSettings#serializable()
|
||||
*/
|
||||
boolean serializable() default false;
|
||||
|
||||
/**
|
||||
* The reset mode to apply to the mock bean.
|
||||
* The default is {@link MockReset#AFTER} meaning that mocks are
|
||||
* The reset mode to apply to the mock.
|
||||
* <p>The default is {@link MockReset#AFTER} meaning that mocks are
|
||||
* automatically reset after each test method is invoked.
|
||||
* @return the reset mode
|
||||
*/
|
||||
|
||||
@@ -40,7 +40,8 @@ public class MockitoBeanOverrideProcessor implements BeanOverrideProcessor {
|
||||
else if (overrideAnnotation instanceof MockitoSpyBean spyBean) {
|
||||
return new SpyDefinition(spyBean, field, typeToMock);
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid annotation for MockitoBeanOverrideProcessor: " + overrideAnnotation.getClass().getName());
|
||||
throw new IllegalArgumentException("Invalid annotation for MockitoBeanOverrideProcessor: " +
|
||||
overrideAnnotation.getClass().getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-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.
|
||||
@@ -24,11 +24,13 @@ import java.util.List;
|
||||
* Beans created using Mockito.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 6.2
|
||||
*/
|
||||
class MockitoBeans implements Iterable<Object> {
|
||||
|
||||
private final List<Object> beans = new ArrayList<>();
|
||||
|
||||
|
||||
void add(Object bean) {
|
||||
this.beans.add(bean);
|
||||
}
|
||||
|
||||
@@ -32,11 +32,10 @@ import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.NativeDetector;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.TestExecutionListener;
|
||||
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
||||
|
||||
/**
|
||||
* {@link TestExecutionListener} to reset any mock beans that have been marked
|
||||
* {@code TestExecutionListener} that resets any mock beans that have been marked
|
||||
* with a {@link MockReset}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
|
||||
@@ -34,11 +34,13 @@ import org.springframework.test.bean.override.BeanOverride;
|
||||
* in the context.
|
||||
*
|
||||
* <p>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)
|
||||
* (such as those
|
||||
* {@link org.springframework.beans.factory.config.ConfigurableListableBeanFactory#registerResolvableDependency(Class, Object)
|
||||
* registered directly}) will not be found.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @since 6.2
|
||||
* @see MockitoBean
|
||||
*/
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@@ -47,16 +49,16 @@ import org.springframework.test.bean.override.BeanOverride;
|
||||
public @interface MockitoSpyBean {
|
||||
|
||||
/**
|
||||
* The name of the bean to spy. If not specified, it will be the name of the
|
||||
* annotated field.
|
||||
* The name of the bean to spy.
|
||||
* <p>If not specified, the name of the annotated field will be used.
|
||||
* @return the name of the spied bean
|
||||
*/
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* The reset mode to apply to the spied bean. The default is
|
||||
* {@link MockReset#AFTER} meaning that spies are automatically reset after
|
||||
* each test method is invoked.
|
||||
* The reset mode to apply to the spied bean.
|
||||
* <p>The default is {@link MockReset#AFTER} meaning that spies are automatically
|
||||
* reset after each test method is invoked.
|
||||
* @return the reset mode
|
||||
*/
|
||||
MockReset reset() default MockReset.AFTER;
|
||||
@@ -65,10 +67,11 @@ public @interface MockitoSpyBean {
|
||||
* Indicates that Mockito methods such as {@link Mockito#verify(Object)
|
||||
* verify(mock)} should use the {@code target} of AOP advised beans,
|
||||
* rather than the proxy itself.
|
||||
* If set to {@code false} you may need to use the result of
|
||||
* <p>Defaults to {@code true}.
|
||||
* <p>If set to {@code false} you may need to use the result of
|
||||
* {@link org.springframework.test.util.AopTestUtils#getUltimateTargetObject(Object)
|
||||
* AopTestUtils.getUltimateTargetObject(...)} when calling Mockito methods.
|
||||
* @return {@code true} if the target of AOP advised beans is used or
|
||||
* @return {@code true} if the target of AOP advised beans is used, or
|
||||
* {@code false} if the proxy is used directly
|
||||
*/
|
||||
boolean proxyTargetAware() default true;
|
||||
|
||||
@@ -25,7 +25,6 @@ import org.mockito.Captor;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.TestExecutionListener;
|
||||
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -33,27 +32,28 @@ import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.ReflectionUtils.FieldCallback;
|
||||
|
||||
/**
|
||||
* {@link TestExecutionListener} to enable {@link MockitoBean @MockitoBean} and
|
||||
* {@code TestExecutionListener} that enables {@link MockitoBean @MockitoBean} and
|
||||
* {@link MockitoSpyBean @MockitoSpyBean} support. Also triggers
|
||||
* {@link MockitoAnnotations#openMocks(Object)} when any Mockito annotations are
|
||||
* used, primarily to allow {@link Captor @Captor} annotations.
|
||||
* <p>
|
||||
* The automatic reset support of {@code @MockBean} and {@code @SpyBean} is
|
||||
* handled by sibling {@link MockitoResetTestExecutionListener}.
|
||||
* used, primarily to support {@link Captor @Captor} annotations.
|
||||
*
|
||||
* <p>The automatic reset support for {@code @MockBean} and {@code @SpyBean} is
|
||||
* handled by the {@link MockitoResetTestExecutionListener}.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @author Moritz Halbritter
|
||||
* @since 1.4.2
|
||||
* @since 6.2
|
||||
* @see MockitoResetTestExecutionListener
|
||||
*/
|
||||
public class MockitoTestExecutionListener extends AbstractTestExecutionListener {
|
||||
|
||||
private static final String MOCKS_ATTRIBUTE_NAME = MockitoTestExecutionListener.class.getName() + ".mocks";
|
||||
|
||||
static final boolean mockitoPresent = ClassUtils.isPresent("org.mockito.MockSettings",
|
||||
MockitoTestExecutionListener.class.getClassLoader());
|
||||
|
||||
private static final String MOCKS_ATTRIBUTE_NAME = MockitoTestExecutionListener.class.getName() + ".mocks";
|
||||
|
||||
/**
|
||||
* Executes before {@link DependencyInjectionTestExecutionListener}.
|
||||
@@ -109,22 +109,23 @@ public class MockitoTestExecutionListener extends AbstractTestExecutionListener
|
||||
}
|
||||
|
||||
private boolean hasMockitoAnnotations(TestContext testContext) {
|
||||
MockitoAnnotationCollection collector = new MockitoAnnotationCollection();
|
||||
MockitoAnnotationCollector collector = new MockitoAnnotationCollector();
|
||||
ReflectionUtils.doWithFields(testContext.getTestClass(), collector);
|
||||
return collector.hasAnnotations();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link FieldCallback} to collect Mockito annotations.
|
||||
* {@link FieldCallback} that collects Mockito annotations.
|
||||
*/
|
||||
private static final class MockitoAnnotationCollection implements FieldCallback {
|
||||
private static final class MockitoAnnotationCollector implements FieldCallback {
|
||||
|
||||
private final Set<Annotation> annotations = new LinkedHashSet<>();
|
||||
|
||||
@Override
|
||||
public void doWith(Field field) throws IllegalArgumentException {
|
||||
for (Annotation annotation : field.getDeclaredAnnotations()) {
|
||||
if (annotation.annotationType().getName().startsWith("org.mockito")) {
|
||||
for (Annotation annotation : field.getAnnotations()) {
|
||||
if (annotation.annotationType().getPackageName().startsWith("org.mockito")) {
|
||||
this.annotations.add(annotation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
* Copyright 2012-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.
|
||||
@@ -19,7 +19,6 @@ package org.springframework.test.bean.override.mockito;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.mockito.AdditionalAnswers;
|
||||
import org.mockito.MockSettings;
|
||||
@@ -43,6 +42,7 @@ import static org.mockito.Mockito.mock;
|
||||
* A complete definition that can be used to create a Mockito spy.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.2
|
||||
*/
|
||||
class SpyDefinition extends Definition {
|
||||
|
||||
@@ -53,49 +53,53 @@ class SpyDefinition extends Definition {
|
||||
|
||||
SpyDefinition(String name, MockReset reset, boolean proxyTargetAware, Field field, Annotation annotation,
|
||||
ResolvableType typeToSpy) {
|
||||
|
||||
super(name, reset, proxyTargetAware, field, annotation, typeToSpy, BeanOverrideStrategy.WRAP_EARLY_BEAN);
|
||||
Assert.notNull(typeToSpy, "typeToSpy must not be null");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getBeanOverrideDescription() {
|
||||
return "spy";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition, @Nullable Object existingBeanInstance) {
|
||||
return createSpy(beanName, Objects.requireNonNull(existingBeanInstance,
|
||||
"MockitoSpyBean requires an existing bean instance for bean " + beanName));
|
||||
protected Object createOverride(String beanName, @Nullable BeanDefinition existingBeanDefinition,
|
||||
@Nullable Object existingBeanInstance) {
|
||||
|
||||
Assert.notNull(existingBeanInstance,
|
||||
() -> "MockitoSpyBean requires an existing bean instance for bean " + beanName);
|
||||
return createSpy(beanName, existingBeanInstance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
//for SpyBean we want the class to be exactly the same
|
||||
// For SpyBean we want the class to be exactly the same.
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || obj.getClass() != getClass()) {
|
||||
return false;
|
||||
}
|
||||
SpyDefinition other = (SpyDefinition) obj;
|
||||
boolean result = super.equals(obj);
|
||||
result = result && ObjectUtils.nullSafeEquals(this.typeToOverride(), other.typeToOverride());
|
||||
return result;
|
||||
SpyDefinition that = (SpyDefinition) obj;
|
||||
return (super.equals(obj) && ObjectUtils.nullSafeEquals(typeToOverride(), that.typeToOverride()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.typeToOverride());
|
||||
result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(typeToOverride());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append("name", this.name)
|
||||
.append("typeToSpy", typeToOverride())
|
||||
.append("reset", getReset())
|
||||
.toString();
|
||||
return new ToStringCreator(this)
|
||||
.append("name", this.name)
|
||||
.append("typeToSpy", typeToOverride())
|
||||
.append("reset", getReset())
|
||||
.toString();
|
||||
}
|
||||
|
||||
<T> T createSpy(Object instance) {
|
||||
@@ -105,7 +109,9 @@ class SpyDefinition extends Definition {
|
||||
@SuppressWarnings("unchecked")
|
||||
<T> T createSpy(String name, Object instance) {
|
||||
Assert.notNull(instance, "Instance must not be null");
|
||||
Assert.isInstanceOf(Objects.requireNonNull(this.typeToOverride().resolve()), instance);
|
||||
Class<?> resolvedTypeToOverride = typeToOverride().resolve();
|
||||
Assert.notNull(resolvedTypeToOverride, "Failed to resolve type to override");
|
||||
Assert.isInstanceOf(resolvedTypeToOverride, instance);
|
||||
if (Mockito.mockingDetails(instance).isSpy()) {
|
||||
return (T) instance;
|
||||
}
|
||||
@@ -119,7 +125,7 @@ class SpyDefinition extends Definition {
|
||||
Class<?> toSpy;
|
||||
if (Proxy.isProxyClass(instance.getClass())) {
|
||||
settings.defaultAnswer(AdditionalAnswers.delegatesTo(instance));
|
||||
toSpy = this.typeToOverride().toClass();
|
||||
toSpy = typeToOverride().toClass();
|
||||
}
|
||||
else {
|
||||
settings.defaultAnswer(Mockito.CALLS_REAL_METHODS);
|
||||
|
||||
Reference in New Issue
Block a user