diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java
index 1ee455db2fe..90229ad7889 100644
--- a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java
+++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java
@@ -41,38 +41,41 @@ import org.springframework.core.ResolvableType;
import org.springframework.util.StringUtils;
/**
- * A {@link BeanFactoryPostProcessor} implementation that processes test classes
- * and adapts the {@link BeanFactory} for any {@link BeanOverride} it may define.
+ * A {@link BeanFactoryPostProcessor} implementation that processes identified
+ * use of {@link BeanOverride} and adapts the {@link BeanFactory} accordingly.
*
- *
A set of classes from which to parse {@link OverrideMetadata} must be
- * provided to this processor. Each test class is expected to use any
- * annotation meta-annotated with {@link BeanOverride @BeanOverride} to mark
- * beans to override. The {@link BeanOverrideParsingUtils#hasBeanOverride(Class)}
- * method can be used to check if a class matches the above criteria.
+ *
For each override, the bean factory is prepared according to the chosen
+ * {@link BeanOverrideStrategy overriding strategy}. The override value is created,
+ * if necessary, and the necessary infrastructure is updated to allow the value
+ * to be injected in the corresponding {@linkplain OverrideMetadata#getField() field}
+ * of the test class.
*
- *
The provided classes are fully parsed at creation to build a metadata set.
- * This processor implements several {@link BeanOverrideStrategy overriding
- * strategies} and chooses the correct one according to each override metadata's
- * {@link OverrideMetadata#getStrategy()} method. Additionally, it provides
- * support for injecting the overridden bean instances into their corresponding
- * annotated {@link Field fields}.
+ *
This processor does not work against a particular test class, it only prepares
+ * the bean factory for the identified, unique, set of bean overrides.
*
* @author Simon Baslé
+ * @author Stephane Nicoll
* @since 6.2
*/
class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {
+ private final Set metadata;
+
private final BeanOverrideRegistrar overrideRegistrar;
/**
* Create a new {@code BeanOverrideBeanFactoryPostProcessor} instance with
- * the given {@link BeanOverrideRegistrar}, which contains a set of parsed
- * {@link OverrideMetadata}.
+ * the set of {@link OverrideMetadata} to process, using the given
+ * {@link BeanOverrideRegistrar}.
+ * @param metadata the {@link OverrideMetadata} instances to process
* @param overrideRegistrar the {@link BeanOverrideRegistrar} used to track
* metadata
*/
- public BeanOverrideBeanFactoryPostProcessor(BeanOverrideRegistrar overrideRegistrar) {
+ public BeanOverrideBeanFactoryPostProcessor(Set metadata,
+ BeanOverrideRegistrar overrideRegistrar) {
+
+ this.metadata = metadata;
this.overrideRegistrar = overrideRegistrar;
}
@@ -92,7 +95,7 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
}
private void postProcessWithRegistry(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry) {
- for (OverrideMetadata metadata : this.overrideRegistrar.getOverrideMetadata()) {
+ for (OverrideMetadata metadata : this.metadata) {
registerBeanOverride(beanFactory, registry, metadata);
}
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizer.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizer.java
index 423a41a98c0..d74740aa8b8 100644
--- a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizer.java
+++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizer.java
@@ -49,10 +49,10 @@ class BeanOverrideContextCustomizer implements ContextCustomizer {
"org.springframework.test.context.bean.override.internalWrapEarlyBeanPostProcessor";
- private final Set> detectedClasses;
+ private final Set metadata;
- BeanOverrideContextCustomizer(Set> detectedClasses) {
- this.detectedClasses = detectedClasses;
+ BeanOverrideContextCustomizer(Set metadata) {
+ this.metadata = metadata;
}
@Override
@@ -61,19 +61,25 @@ class BeanOverrideContextCustomizer implements ContextCustomizer {
throw new IllegalStateException("Cannot process bean overrides with an ApplicationContext " +
"that doesn't implement BeanDefinitionRegistry: " + context.getClass());
}
- registerInfrastructure(registry, this.detectedClasses);
+ registerInfrastructure(registry);
}
- private void registerInfrastructure(BeanDefinitionRegistry registry, Set> detectedClasses) {
+ Set getMetadata() {
+ return this.metadata;
+ }
+
+ private void registerInfrastructure(BeanDefinitionRegistry registry) {
addInfrastructureBeanDefinition(registry, BeanOverrideRegistrar.class, REGISTRAR_BEAN_NAME,
- constructorArgs -> constructorArgs.addIndexedArgumentValue(0, detectedClasses));
+ constructorArgs -> {});
+
RuntimeBeanReference registrarReference = new RuntimeBeanReference(REGISTRAR_BEAN_NAME);
- addInfrastructureBeanDefinition(
- registry, WrapEarlyBeanPostProcessor.class, EARLY_INFRASTRUCTURE_BEAN_NAME,
- constructorArgs -> constructorArgs.addIndexedArgumentValue(0, registrarReference));
- addInfrastructureBeanDefinition(
- registry, BeanOverrideBeanFactoryPostProcessor.class, INFRASTRUCTURE_BEAN_NAME,
+ addInfrastructureBeanDefinition(registry, WrapEarlyBeanPostProcessor.class, EARLY_INFRASTRUCTURE_BEAN_NAME,
constructorArgs -> constructorArgs.addIndexedArgumentValue(0, registrarReference));
+ addInfrastructureBeanDefinition(registry, BeanOverrideBeanFactoryPostProcessor.class, INFRASTRUCTURE_BEAN_NAME,
+ constructorArgs -> {
+ constructorArgs.addIndexedArgumentValue(0, this.metadata);
+ constructorArgs.addIndexedArgumentValue(1, registrarReference);
+ });
}
private void addInfrastructureBeanDefinition(BeanDefinitionRegistry registry,
@@ -97,12 +103,12 @@ class BeanOverrideContextCustomizer implements ContextCustomizer {
return false;
}
BeanOverrideContextCustomizer that = (BeanOverrideContextCustomizer) other;
- return this.detectedClasses.equals(that.detectedClasses);
+ return this.metadata.equals(that.metadata);
}
@Override
public int hashCode() {
- return this.detectedClasses.hashCode();
+ return this.metadata.hashCode();
}
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactory.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactory.java
index 73b072a62be..4b79f97d456 100644
--- a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactory.java
+++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactory.java
@@ -16,13 +16,12 @@
package org.springframework.test.context.bean.override;
-import java.util.LinkedHashSet;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.lang.Nullable;
import org.springframework.test.context.ContextConfigurationAttributes;
-import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.TestContextAnnotationUtils;
@@ -31,6 +30,7 @@ import org.springframework.test.context.TestContextAnnotationUtils;
* Bean Overriding.
*
* @author Simon Baslé
+ * @author Stephane Nicoll
* @since 6.2
* @see BeanOverride
*/
@@ -38,24 +38,22 @@ class BeanOverrideContextCustomizerFactory implements ContextCustomizerFactory {
@Override
@Nullable
- public ContextCustomizer createContextCustomizer(Class> testClass,
+ public BeanOverrideContextCustomizer createContextCustomizer(Class> testClass,
List configAttributes) {
- Set> detectedClasses = new LinkedHashSet<>();
- findClassesWithBeanOverride(testClass, detectedClasses);
- if (detectedClasses.isEmpty()) {
+ Set metadata = new HashSet<>();
+ findOverrideMetadata(testClass, metadata);
+ if (metadata.isEmpty()) {
return null;
}
-
- return new BeanOverrideContextCustomizer(detectedClasses);
+ return new BeanOverrideContextCustomizer(metadata);
}
- private void findClassesWithBeanOverride(Class> testClass, Set> detectedClasses) {
- if (BeanOverrideParsingUtils.hasBeanOverride(testClass)) {
- detectedClasses.add(testClass);
- }
+ private void findOverrideMetadata(Class> testClass, Set metadata) {
+ List overrideMetadata = OverrideMetadata.forTestClass(testClass);
+ metadata.addAll(overrideMetadata);
if (TestContextAnnotationUtils.searchEnclosingClass(testClass)) {
- findClassesWithBeanOverride(testClass.getEnclosingClass(), detectedClasses);
+ findOverrideMetadata(testClass.getEnclosingClass(), metadata);
}
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideParsingUtils.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideParsingUtils.java
deleted file mode 100644
index 3c0984ec8c8..00000000000
--- a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideParsingUtils.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2002-2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.test.context.bean.override;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Field;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.springframework.beans.BeanUtils;
-import org.springframework.core.annotation.MergedAnnotation;
-import org.springframework.core.annotation.MergedAnnotations;
-import org.springframework.util.Assert;
-import org.springframework.util.ReflectionUtils;
-
-import static org.springframework.core.annotation.MergedAnnotations.SearchStrategy.DIRECT;
-
-/**
- * Internal parsing utilities to discover the presence of
- * {@link BeanOverride @BeanOverride} and create the relevant
- * {@link OverrideMetadata} if necessary.
- *
- * @author Simon Baslé
- * @author Sam Brannen
- * @since 6.2
- */
-abstract class BeanOverrideParsingUtils {
-
- /**
- * Check if at least one field of the given {@code clazz} is meta-annotated
- * with {@link BeanOverride @BeanOverride}.
- * @param clazz the class which fields to inspect
- * @return {@code true} if there is a bean override annotation present,
- * {@code false} otherwise
- */
- static boolean hasBeanOverride(Class> clazz) {
- AtomicBoolean hasBeanOverride = new AtomicBoolean();
- ReflectionUtils.doWithFields(clazz, field -> {
- if (hasBeanOverride.get()) {
- return;
- }
- boolean present = MergedAnnotations.from(field, DIRECT).isPresent(BeanOverride.class);
- hasBeanOverride.compareAndSet(false, present);
- });
- return hasBeanOverride.get();
- }
-
- /**
- * Parse the specified classes for the presence of fields annotated with
- * {@link BeanOverride @BeanOverride}, and create an {@link OverrideMetadata}
- * for each identified field.
- * @param classes the classes to parse
- */
- static Set parse(Iterable> classes) {
- Set result = new LinkedHashSet<>();
- classes.forEach(c -> ReflectionUtils.doWithFields(c, field -> parseField(field, c, result)));
- return result;
- }
-
- /**
- * Convenience method to parse a single test class.
- * @see #parse(Iterable)
- */
- static Set parse(Class> clazz) {
- return parse(List.of(clazz));
- }
-
- private static void parseField(Field field, Class> testClass, Set metadataSet) {
- AtomicBoolean overrideAnnotationFound = new AtomicBoolean();
- MergedAnnotations.from(field, DIRECT).stream(BeanOverride.class).forEach(mergedAnnotation -> {
- MergedAnnotation> metaSource = mergedAnnotation.getMetaSource();
- Assert.state(metaSource != null, "@BeanOverride annotation must be meta-present");
-
- BeanOverride beanOverride = mergedAnnotation.synthesize();
- BeanOverrideProcessor processor = BeanUtils.instantiateClass(beanOverride.value());
- Annotation composedAnnotation = metaSource.synthesize();
-
- Assert.state(overrideAnnotationFound.compareAndSet(false, true),
- () -> "Multiple @BeanOverride annotations found on field: " + field);
- OverrideMetadata metadata = processor.createMetadata(composedAnnotation, testClass, field);
- metadataSet.add(metadata);
- });
- }
-
-}
diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideRegistrar.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideRegistrar.java
index e8a8bfd30d1..2dafdcf23cf 100644
--- a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideRegistrar.java
+++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideRegistrar.java
@@ -19,7 +19,6 @@ package org.springframework.test.context.bean.override;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
-import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
@@ -45,21 +44,10 @@ class BeanOverrideRegistrar implements BeanFactoryAware {
private final Map earlyOverrideMetadata = new HashMap<>();
- private final Set overrideMetadata;
-
@Nullable
private ConfigurableBeanFactory beanFactory;
- /**
- * Create a new registrar and immediately parse the provided classes.
- * @param classesToParse the initial set of classes that have been
- * detected to contain bean overriding annotations
- */
- BeanOverrideRegistrar(Set> classesToParse) {
- this.overrideMetadata = BeanOverrideParsingUtils.parse(classesToParse);
- }
-
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (!(beanFactory instanceof ConfigurableBeanFactory cbf)) {
@@ -69,13 +57,6 @@ class BeanOverrideRegistrar implements BeanFactoryAware {
this.beanFactory = cbf;
}
- /**
- * Get the detected {@link OverrideMetadata} instances.
- */
- Set getOverrideMetadata() {
- return this.overrideMetadata;
- }
-
/**
* Check {@link #markWrapEarly(OverrideMetadata, String) early override}
* records and use the {@link OverrideMetadata} to create an override
diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideTestExecutionListener.java
index e84b65cf422..10a59a5d301 100644
--- a/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideTestExecutionListener.java
+++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideTestExecutionListener.java
@@ -17,6 +17,7 @@
package org.springframework.test.context.bean.override;
import java.lang.reflect.Field;
+import java.util.List;
import java.util.function.BiConsumer;
import org.springframework.test.context.TestContext;
@@ -74,12 +75,12 @@ public class BeanOverrideTestExecutionListener extends AbstractTestExecutionList
if (Boolean.TRUE.equals(
testContext.getAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE))) {
- postProcessFields(testContext, (testMetadata, postProcessor) -> {
+ postProcessFields(testContext, (testMetadata, registrar) -> {
Object testInstance = testMetadata.testInstance;
Field field = testMetadata.overrideMetadata.getField();
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, testInstance, null);
- postProcessor.inject(testInstance, testMetadata.overrideMetadata);
+ registrar.inject(testInstance, testMetadata.overrideMetadata);
});
}
}
@@ -90,13 +91,11 @@ public class BeanOverrideTestExecutionListener extends AbstractTestExecutionList
Class> testClass = testContext.getTestClass();
Object testInstance = testContext.getTestInstance();
- if (BeanOverrideParsingUtils.hasBeanOverride(testClass)) {
+ List metadataForFields = OverrideMetadata.forTestClass(testClass);
+ if (!metadataForFields.isEmpty()) {
BeanOverrideRegistrar registrar =
testContext.getApplicationContext().getBean(BeanOverrideRegistrar.class);
- for (OverrideMetadata metadata : registrar.getOverrideMetadata()) {
- if (!metadata.getField().getDeclaringClass().isAssignableFrom(testClass)) {
- continue;
- }
+ for (OverrideMetadata metadata : metadataForFields) {
consumer.accept(new TestContextOverrideMetadata(testInstance, metadata), registrar);
}
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/OverrideMetadata.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/OverrideMetadata.java
index 08510af51bb..c7a323e354a 100644
--- a/spring-test/src/main/java/org/springframework/test/context/bean/override/OverrideMetadata.java
+++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/OverrideMetadata.java
@@ -16,14 +16,26 @@
package org.springframework.test.context.bean.override;
+import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.SingletonBeanRegistry;
import org.springframework.core.ResolvableType;
+import org.springframework.core.annotation.MergedAnnotation;
+import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.style.ToStringCreator;
import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.util.ReflectionUtils;
+
+import static org.springframework.core.annotation.MergedAnnotations.SearchStrategy.DIRECT;
/**
* Metadata for Bean Override injection points, also responsible for creation of
@@ -61,6 +73,34 @@ public abstract class OverrideMetadata {
this.strategy = strategy;
}
+ /**
+ * Parse the given {@code testClass} and provide the use of bean override.
+ * @param testClass the class to parse
+ * @return a list of bean overrides metadata
+ */
+ public static List forTestClass(Class> testClass) {
+ List all = new LinkedList<>();
+ ReflectionUtils.doWithFields(testClass, field -> parseField(field, testClass, all));
+ return all;
+ }
+
+ private static void parseField(Field field, Class> testClass, List metadataList) {
+ AtomicBoolean overrideAnnotationFound = new AtomicBoolean();
+ MergedAnnotations.from(field, DIRECT).stream(BeanOverride.class).forEach(mergedAnnotation -> {
+ MergedAnnotation> metaSource = mergedAnnotation.getMetaSource();
+ Assert.state(metaSource != null, "@BeanOverride annotation must be meta-present");
+
+ BeanOverride beanOverride = mergedAnnotation.synthesize();
+ BeanOverrideProcessor processor = BeanUtils.instantiateClass(beanOverride.value());
+ Annotation composedAnnotation = metaSource.synthesize();
+
+ Assert.state(overrideAnnotationFound.compareAndSet(false, true),
+ () -> "Multiple @BeanOverride annotations found on field: " + field);
+ OverrideMetadata metadata = processor.createMetadata(composedAnnotation, testClass, field);
+ metadataList.add(metadata);
+ });
+ }
+
/**
* Get the annotated {@link Field}.
@@ -127,15 +167,16 @@ public abstract class OverrideMetadata {
return false;
}
OverrideMetadata that = (OverrideMetadata) obj;
- return Objects.equals(this.beanType, that.beanType) &&
+ return Objects.equals(this.beanType.getType(), that.beanType.getType()) &&
Objects.equals(this.beanName, that.beanName) &&
Objects.equals(this.strategy, that.strategy) &&
- Objects.equals(this.field, that.field);
+ Arrays.equals(this.field.getAnnotations(), that.field.getAnnotations());
}
@Override
public int hashCode() {
- return Objects.hash(this.beanType, this.beanName, this.strategy, this.field);
+ return Objects.hash(this.beanType.getType(), this.beanName, this.strategy,
+ Arrays.hashCode(this.field.getAnnotations()));
}
@Override
diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideMetadata.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideMetadata.java
index b9510822380..675094baff2 100644
--- a/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideMetadata.java
+++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideMetadata.java
@@ -79,6 +79,6 @@ final class TestBeanOverrideMetadata extends OverrideMetadata {
@Override
public int hashCode() {
- return Objects.hash(super.hashCode(), this.overrideMethod);
+ return this.overrideMethod.hashCode() * 29 + super.hashCode();
}
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideMetadata.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideMetadata.java
index de5822a9245..a4e7e5a14c1 100644
--- a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideMetadata.java
+++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideMetadata.java
@@ -43,6 +43,7 @@ import static org.mockito.Mockito.mock;
* {@link OverrideMetadata} implementation for Mockito {@code mock} support.
*
* @author Phillip Webb
+ * @author Stephane Nicoll
* @since 6.2
*/
class MockitoBeanOverrideMetadata extends MockitoOverrideMetadata {
@@ -142,7 +143,7 @@ class MockitoBeanOverrideMetadata extends MockitoOverrideMetadata {
@Override
public int hashCode() {
- return Objects.hash(super.hashCode(), this.extraInterfaces, this.answer, this.serializable);
+ return Objects.hash(this.extraInterfaces, this.answer, this.serializable) + super.hashCode();
}
@Override
diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoOverrideMetadata.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoOverrideMetadata.java
index d2c06a4794c..d024a105b56 100644
--- a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoOverrideMetadata.java
+++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoOverrideMetadata.java
@@ -31,6 +31,7 @@ import org.springframework.util.ObjectUtils;
* Base {@link OverrideMetadata} implementation for Mockito.
*
* @author Phillip Webb
+ * @author Stephane Nicoll
* @since 6.2
*/
abstract class MockitoOverrideMetadata extends OverrideMetadata {
@@ -98,7 +99,7 @@ abstract class MockitoOverrideMetadata extends OverrideMetadata {
@Override
public int hashCode() {
- return Objects.hash(super.hashCode(), this.reset, this.proxyTargetAware);
+ return Objects.hash(this.reset, this.proxyTargetAware) + super.hashCode();
}
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideMetadata.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideMetadata.java
index 8910472e309..5078a5dab59 100644
--- a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideMetadata.java
+++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideMetadata.java
@@ -18,7 +18,6 @@ package org.springframework.test.context.bean.override.mockito;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
-import java.util.Objects;
import org.mockito.AdditionalAnswers;
import org.mockito.MockSettings;
@@ -34,7 +33,6 @@ import org.springframework.test.context.bean.override.BeanOverrideStrategy;
import org.springframework.test.context.bean.override.OverrideMetadata;
import org.springframework.test.util.AopTestUtils;
import org.springframework.util.Assert;
-import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import static org.mockito.Mockito.mock;
@@ -44,6 +42,7 @@ import static org.mockito.Mockito.mock;
*
* @author Phillip Webb
* @author Simon Baslé
+ * @author Stephane Nicoll
* @since 6.2
*/
class MockitoSpyBeanOverrideMetadata extends MockitoOverrideMetadata {
@@ -108,13 +107,12 @@ class MockitoSpyBeanOverrideMetadata extends MockitoOverrideMetadata {
if (other == null || other.getClass() != getClass()) {
return false;
}
- MockitoSpyBeanOverrideMetadata that = (MockitoSpyBeanOverrideMetadata) other;
- return (super.equals(that) && ObjectUtils.nullSafeEquals(getBeanType(), that.getBeanType()));
+ return super.equals(other);
}
@Override
public int hashCode() {
- return Objects.hash(super.hashCode(), getBeanType());
+ return getClass().hashCode() * 29 + super.hashCode();
}
@Override
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessorTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessorTests.java
index e2805cf0885..b603cb6df40 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessorTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessorTests.java
@@ -17,6 +17,7 @@
package org.springframework.test.context.bean.override;
import java.lang.reflect.Field;
+import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
@@ -203,7 +204,8 @@ class BeanOverrideBeanFactoryPostProcessorTests {
private AnnotationConfigApplicationContext createContext(Class> testClass) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
- new BeanOverrideContextCustomizer(Set.of(testClass)).customizeContext(context, mock(MergedContextConfiguration.class));
+ Set metadata = new LinkedHashSet<>(OverrideMetadata.forTestClass(testClass));
+ new BeanOverrideContextCustomizer(metadata).customizeContext(context, mock(MergedContextConfiguration.class));
return context;
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerEqualityTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerEqualityTests.java
new file mode 100644
index 00000000000..289d2395b59
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerEqualityTests.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.bean.override;
+
+import java.util.Collections;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.Answers;
+
+import org.springframework.test.context.ContextCustomizer;
+import org.springframework.test.context.bean.override.convention.TestBean;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
+import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Bean override tests that validate the behavior with the TCF context cache.
+ *
+ * @author Stephane Nicoll
+ */
+@SuppressWarnings("unused")
+class BeanOverrideContextCustomizerEqualityTests {
+
+ private final BeanOverrideContextCustomizerFactory factory = new BeanOverrideContextCustomizerFactory();
+
+ @Nested
+ class SameContextTests {
+
+ @Test
+ void testsWithOneIdenticalTestBean() {
+ assertThat(createContextCustomizer(Case1.class)).isEqualTo(createContextCustomizer(Case2.class));
+ }
+
+ @Test
+ void testsWithOneIdenticalMockitoMockBean() {
+ assertThat(createContextCustomizer(Case4.class)).isEqualTo(createContextCustomizer(Case5.class));
+ }
+
+ @Test
+ void testsWithOneIdenticalMockitoSpyBean() {
+ assertThat(createContextCustomizer(Case7.class)).isEqualTo(createContextCustomizer(Case8.class));
+ }
+ }
+
+ @Nested
+ class DifferentContextTests {
+
+ @Test
+ void testsWithSimilarTestBeanButDifferentMethod() {
+ assertThat(createContextCustomizer(Case1.class)).isNotEqualTo(createContextCustomizer(Case3.class));
+ }
+
+ @Test
+ void testsWithSimilarMockitoMockButDifferentAnswers() {
+ assertThat(createContextCustomizer(Case4.class)).isNotEqualTo(createContextCustomizer(Case6.class));
+ }
+
+ @Test
+ void testsWithSimilarMockitoSpyButDifferentProxyTargetClass() {
+ assertThat(createContextCustomizer(Case8.class)).isNotEqualTo(createContextCustomizer(Case9.class));
+ }
+
+ @Test
+ void testsWithSameConfigurationButOneIsMockitoBeanAndTheOtherMockitoSpy() {
+ assertThat(createContextCustomizer(Case4.class)).isNotEqualTo(createContextCustomizer(Case7.class));
+ }
+
+ }
+
+
+ private ContextCustomizer createContextCustomizer(Class> testClass) {
+ BeanOverrideContextCustomizer customizer = this.factory.createContextCustomizer(
+ testClass, Collections.emptyList());
+ assertThat(customizer).isNotNull();
+ return customizer;
+ }
+
+ interface DescriptionProvider {
+
+ static String createDescription() {
+ return "override";
+ }
+
+ }
+
+ static class Case1 implements DescriptionProvider {
+
+ @TestBean(methodName = "createDescription")
+ private String description;
+
+ }
+
+ static class Case2 implements DescriptionProvider {
+
+ @TestBean(methodName = "createDescription")
+ private String description;
+
+ }
+
+ static class Case3 implements DescriptionProvider {
+
+ @TestBean(methodName = "createDescription")
+ private String description;
+
+ static String createDescription() {
+ return "another value";
+ }
+ }
+
+ static class Case4 {
+
+ @MockitoBean
+ private String exampleService;
+
+ }
+
+ static class Case5 {
+
+ @MockitoBean
+ private String serviceToMock;
+
+ }
+
+ static class Case6 {
+
+ @MockitoBean(answers = Answers.RETURNS_MOCKS)
+ private String exampleService;
+
+ }
+
+ static class Case7 {
+
+ @MockitoSpyBean
+ private String exampleService;
+
+ }
+
+ static class Case8 {
+
+ @MockitoSpyBean
+ private String serviceToMock;
+
+ }
+
+ static class Case9 {
+
+ @MockitoSpyBean(proxyTargetAware = false)
+ private String serviceToMock;
+
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactoryTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactoryTests.java
new file mode 100644
index 00000000000..d26473270a5
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactoryTests.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.bean.override;
+
+import java.util.Collections;
+import java.util.function.Consumer;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.lang.Nullable;
+import org.springframework.test.context.bean.override.BeanOverrideContextCustomizerFactoryTests.Test2.Green;
+import org.springframework.test.context.bean.override.BeanOverrideContextCustomizerFactoryTests.Test2.Orange;
+import org.springframework.test.context.bean.override.convention.TestBean;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+
+/**
+ * Tests for {@link BeanOverrideContextCustomizerFactory}.
+ *
+ * @author Stephane Nicoll
+ */
+class BeanOverrideContextCustomizerFactoryTests {
+
+ private final BeanOverrideContextCustomizerFactory factory = new BeanOverrideContextCustomizerFactory();
+
+ @Test
+ void createContextCustomizerWhenTestHasNoBeanOverride() {
+ assertThat(createContextCustomizer(String.class)).isNull();
+ }
+
+ @Test
+ void createContextCustomizerWhenTestHasSingleBeanOverride() {
+ BeanOverrideContextCustomizer customizer = createContextCustomizer(Test1.class);
+ assertThat(customizer).isNotNull();
+ assertThat(customizer.getMetadata()).singleElement().satisfies(testMetadata(null, String.class));
+ }
+
+ @Test
+ void createContextCustomizerWhenNestedTestHasSingleBeanOverrideInParent() {
+ BeanOverrideContextCustomizer customizer = createContextCustomizer(Orange.class);
+ assertThat(customizer).isNotNull();
+ assertThat(customizer.getMetadata()).singleElement().satisfies(testMetadata(null, String.class));
+ }
+
+ @Test
+ void createContextCustomizerWhenNestedTestHasBeanOverrideAsWellAsTheParent() {
+ BeanOverrideContextCustomizer customizer = createContextCustomizer(Green.class);
+ assertThat(customizer).isNotNull();
+ assertThat(customizer.getMetadata())
+ .anySatisfy(testMetadata(null, String.class))
+ .anySatisfy(testMetadata("counterBean", Integer.class))
+ .hasSize(2);
+ }
+
+ @Test
+ void createContextCustomizerWhenTestHasInvalidTestBeanTargetMethod() {
+ assertThatIllegalStateException()
+ .isThrownBy(() -> createContextCustomizer(InvalidTestMissingMethod.class))
+ .withMessageContaining("Failed to find a static test bean factory method");
+ }
+
+
+ private Consumer testMetadata(@Nullable String beanName, Class> beanType) {
+ return overrideMetadata(beanName, beanType, BeanOverrideStrategy.REPLACE_DEFINITION);
+ }
+
+ private Consumer overrideMetadata(@Nullable String beanName, Class> beanType, BeanOverrideStrategy strategy) {
+ return metadata -> {
+ assertThat(metadata.getBeanName()).isEqualTo(beanName);
+ assertThat(metadata.getBeanType().toClass()).isEqualTo(beanType);
+ assertThat(metadata.getStrategy()).isEqualTo(strategy);
+ };
+ }
+
+ @Nullable
+ BeanOverrideContextCustomizer createContextCustomizer(Class> testClass) {
+ return this.factory.createContextCustomizer(testClass, Collections.emptyList());
+ }
+
+ static class Test1 {
+
+ @TestBean(methodName = "descriptor")
+ private String descriptor;
+
+ private static String descriptor() {
+ return "Overridden descriptor";
+ }
+
+ }
+
+ static class Test2 {
+
+ @TestBean(methodName = "name")
+ private String name;
+
+ private static String name() {
+ return "Overridden name";
+ }
+
+ @Nested
+ class Orange {
+
+ }
+
+ @Nested
+ class Green {
+
+ @TestBean(name = "counterBean", methodName = "counter")
+ private Integer counter;
+
+ private static Integer counter() {
+ return 42;
+ }
+ }
+ }
+
+ static class InvalidTestMissingMethod {
+
+ @TestBean(methodName = "descriptor")
+ private String descriptor;
+
+ private String descriptor() {
+ return "Never called, not static";
+ }
+
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerTests.java
new file mode 100644
index 00000000000..f518d08ea58
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerTests.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.bean.override;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Objects;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.core.ResolvableType;
+import org.springframework.lang.Nullable;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link BeanOverrideContextCustomizer}.
+ *
+ * @author Stephane Nicoll
+ */
+class BeanOverrideContextCustomizerTests {
+
+ @Test
+ void customizerIsEqualWithIdenticalMetadata() {
+ BeanOverrideContextCustomizer customizer = createCustomizer(new DummyOverrideMetadata("key"));
+ BeanOverrideContextCustomizer customizer2 = createCustomizer(new DummyOverrideMetadata("key"));
+ assertThat(customizer).isEqualTo(customizer2);
+ assertThat(customizer).hasSameHashCodeAs(customizer2);
+ }
+
+ @Test
+ void customizerIsEqualWithIdenticalMetadataInDifferentOrder() {
+ BeanOverrideContextCustomizer customizer = createCustomizer(
+ new DummyOverrideMetadata("key1"), new DummyOverrideMetadata("key2"));
+ BeanOverrideContextCustomizer customizer2 = createCustomizer(
+ new DummyOverrideMetadata("key2"), new DummyOverrideMetadata("key1"));
+ assertThat(customizer).isEqualTo(customizer2);
+ assertThat(customizer).hasSameHashCodeAs(customizer2);
+ }
+
+ @Test
+ void customizerIsNotEqualWithDifferentMetadata() {
+ BeanOverrideContextCustomizer customizer = createCustomizer(new DummyOverrideMetadata("key"));
+ BeanOverrideContextCustomizer customizer2 = createCustomizer(
+ new DummyOverrideMetadata("key"), new DummyOverrideMetadata("another"));
+ assertThat(customizer).isNotEqualTo(customizer2);
+ }
+
+ private BeanOverrideContextCustomizer createCustomizer(OverrideMetadata... metadata) {
+ return new BeanOverrideContextCustomizer(new LinkedHashSet<>(Arrays.asList(metadata)));
+ }
+
+ private static class DummyOverrideMetadata extends OverrideMetadata {
+
+ private final String key;
+
+ public DummyOverrideMetadata(String key) {
+ super(mock(Field.class), ResolvableType.forClass(Object.class), null, BeanOverrideStrategy.REPLACE_DEFINITION);
+ this.key = key;
+ }
+
+ @Override
+ protected Object createOverride(String beanName, BeanDefinition existingBeanDefinition,
+ Object existingBeanInstance) {
+ return existingBeanInstance;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ DummyOverrideMetadata that = (DummyOverrideMetadata) o;
+ return Objects.equals(this.key, that.key);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.key.hashCode();
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideParsingUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideParsingUtilsTests.java
deleted file mode 100644
index ef134d292ff..00000000000
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideParsingUtilsTests.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright 2002-2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.test.context.bean.override;
-
-import java.lang.reflect.Field;
-
-import org.junit.jupiter.api.Test;
-
-import org.springframework.test.context.bean.override.example.ExampleBeanOverrideAnnotation;
-import org.springframework.test.context.bean.override.example.TestBeanOverrideMetaAnnotation;
-import org.springframework.util.ReflectionUtils;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatRuntimeException;
-
-/**
- * Unit tests for {@link BeanOverrideParsingUtils}.
- *
- * @since 6.2
- */
-class BeanOverrideParsingUtilsTests {
-
- // Metadata built from a String that starts with DUPLICATE_TRIGGER are considered equal
- private static final String DUPLICATE_TRIGGER1 = ExampleBeanOverrideAnnotation.DUPLICATE_TRIGGER + "-v1";
- private static final String DUPLICATE_TRIGGER2 = ExampleBeanOverrideAnnotation.DUPLICATE_TRIGGER + "-v2";
-
- @Test
- void findsOnField() {
- assertThat(BeanOverrideParsingUtils.parse(SingleAnnotationOnField.class))
- .map(Object::toString)
- .containsExactly("onField");
- }
-
- @Test
- void allowsMultipleProcessorsOnDifferentElements() {
- assertThat(BeanOverrideParsingUtils.parse(AnnotationsOnMultipleFields.class))
- .map(Object::toString)
- .containsExactlyInAnyOrder("onField1", "onField2");
- }
-
- @Test
- void rejectsMultipleAnnotationsOnSameElement() {
- Field field = ReflectionUtils.findField(MultipleAnnotationsOnField.class, "message");
- assertThatRuntimeException()
- .isThrownBy(() -> BeanOverrideParsingUtils.parse(MultipleAnnotationsOnField.class))
- .withMessage("Multiple @BeanOverride annotations found on field: " + field);
- }
-
- @Test
- void keepsFirstOccurrenceOfEqualMetadata() {
- assertThat(BeanOverrideParsingUtils.parse(DuplicateConf.class))
- .map(Object::toString)
- .containsExactly("{DUPLICATE-v1}");
- }
-
-
- static class SingleAnnotationOnField {
-
- @ExampleBeanOverrideAnnotation("onField")
- String message;
-
- static String onField() {
- return "OK";
- }
- }
-
- static class MultipleAnnotationsOnField {
-
- @ExampleBeanOverrideAnnotation("foo")
- @TestBeanOverrideMetaAnnotation
- String message;
-
- static String foo() {
- return "foo";
- }
- }
-
- static class AnnotationsOnMultipleFields {
-
- @ExampleBeanOverrideAnnotation("onField1")
- String message;
-
- @ExampleBeanOverrideAnnotation("onField2")
- String messageOther;
-
- static String onField1() {
- return "OK1";
- }
-
- static String onField2() {
- return "OK2";
- }
- }
-
- static class DuplicateConf {
-
- @ExampleBeanOverrideAnnotation(DUPLICATE_TRIGGER1)
- String message1;
-
- @ExampleBeanOverrideAnnotation(DUPLICATE_TRIGGER2)
- String message2;
- }
-
-}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/OverrideMetadataTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/OverrideMetadataTests.java
index 7217d9a902d..8724b393130 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/OverrideMetadataTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/OverrideMetadataTests.java
@@ -16,47 +16,266 @@
package org.springframework.test.context.bean.override;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import java.lang.reflect.Field;
+import java.util.HashSet;
+import java.util.List;
+import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.core.ResolvableType;
-import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
+import org.springframework.test.context.bean.override.example.CustomQualifier;
+import org.springframework.test.context.bean.override.example.ExampleService;
+import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link OverrideMetadata}.
*
* @author Simon Baslé
+ * @author Stephane Nicoll
* @since 6.2
*/
-class OverrideMetadataTests {
+public class OverrideMetadataTests {
@Test
- void implicitConfigurations() throws Exception {
- OverrideMetadata metadata = exampleOverride();
- assertThat(metadata.getBeanName()).as("expectedBeanName").isNull();
+ void forTestClassWithSingleField() {
+ List overrideMetadata = OverrideMetadata.forTestClass(SingleAnnotation.class);
+ assertThat(overrideMetadata).singleElement().satisfies(hasTestBeanMetadata(
+ field(SingleAnnotation.class, "message"), String.class, null));
}
+ @Test
+ void forTestClassWithMultipleFields() {
+ List overrideMetadata = OverrideMetadata.forTestClass(MultipleAnnotations.class);
+ assertThat(overrideMetadata).hasSize(2)
+ .anySatisfy(hasTestBeanMetadata(
+ field(MultipleAnnotations.class, "message"), String.class, null))
+ .anySatisfy(hasTestBeanMetadata(
+ field(MultipleAnnotations.class, "counter"), Integer.class, null));
+ }
+
+ @Test
+ void forTestClassWithMultipleFieldsSameMetadata() {
+ List overrideMetadata = OverrideMetadata.forTestClass(MultipleAnnotationsDuplicate.class);
+ assertThat(overrideMetadata).hasSize(2)
+ .anySatisfy(hasTestBeanMetadata(
+ field(MultipleAnnotationsDuplicate.class, "message1"), String.class, null))
+ .anySatisfy(hasTestBeanMetadata(
+ field(MultipleAnnotationsDuplicate.class, "message2"), String.class, null));
+ assertThat(new HashSet<>(overrideMetadata)).hasSize(1);
+ }
+
+ @Test
+ void forTestClassWithDifferentOverrideMetadataOnSameField() {
+ Field faultyField = field(MultipleAnnotationsOnSameField.class, "message");
+ assertThatIllegalStateException()
+ .isThrownBy(() -> OverrideMetadata.forTestClass(MultipleAnnotationsOnSameField.class))
+ .withMessageStartingWith("Multiple @BeanOverride annotations found")
+ .withMessageContaining(faultyField.toString());
+ }
+
+ @Test
+ void getBeanNameIsNullByDefault() {
+ OverrideMetadata metadata = createMetadata(field(ConfigA.class, "noQualifier"));
+ assertThat(metadata.getBeanName()).isNull();
+ }
+
+ @Test
+ void isEqualToWithSameInstance() {
+ OverrideMetadata metadata = createMetadata(field(ConfigA.class, "noQualifier"));
+ assertThat(metadata).isEqualTo(metadata);
+ assertThat(metadata).hasSameHashCodeAs(metadata);
+ }
+
+ @Test
+ void isEqualToWithSameMetadata() {
+ OverrideMetadata metadata = createMetadata(field(ConfigA.class, "noQualifier"));
+ OverrideMetadata metadata2 = createMetadata(field(ConfigA.class, "noQualifier"));
+ assertThat(metadata).isEqualTo(metadata2);
+ assertThat(metadata).hasSameHashCodeAs(metadata2);
+ }
+
+ @Test
+ void isEqualToWithSameMetadataAndBeanNames() {
+ OverrideMetadata metadata = createMetadata(field(ConfigA.class, "noQualifier"), "testBean");
+ OverrideMetadata metadata2 = createMetadata(field(ConfigA.class, "noQualifier"), "testBean");
+ assertThat(metadata).isEqualTo(metadata2);
+ assertThat(metadata).hasSameHashCodeAs(metadata2);
+ }
+
+ @Test
+ void isNotEqualToWithSameMetadataAndDifferentBeaName() {
+ OverrideMetadata metadata = createMetadata(field(ConfigA.class, "noQualifier"), "testBean");
+ OverrideMetadata metadata2 = createMetadata(field(ConfigA.class, "noQualifier"), "testBean2");
+ assertThat(metadata).isNotEqualTo(metadata2);
+ }
+
+ @Test
+ void isEqualToWithSameMetadataButDifferentFields() {
+ OverrideMetadata metadata = createMetadata(field(ConfigA.class, "noQualifier"));
+ OverrideMetadata metadata2 = createMetadata(field(ConfigB.class, "noQualifier"));
+ assertThat(metadata).isEqualTo(metadata2);
+ assertThat(metadata).hasSameHashCodeAs(metadata2);
+ }
+
+ @Test
+ void isEqualToWithSameMetadataAndSameQualifierValues() {
+ OverrideMetadata metadata = createMetadata(field(ConfigA.class, "directQualifier"));
+ OverrideMetadata metadata2 = createMetadata(field(ConfigB.class, "directQualifier"));
+ assertThat(metadata).isEqualTo(metadata2);
+ assertThat(metadata).hasSameHashCodeAs(metadata2);
+ }
+
+ @Test
+ void isNotEqualToWithSameMetadataAndDifferentQualifierValues() {
+ OverrideMetadata metadata = createMetadata(field(ConfigA.class, "directQualifier"));
+ OverrideMetadata metadata2 = createMetadata(field(ConfigA.class, "differentDirectQualifier"));
+ assertThat(metadata).isNotEqualTo(metadata2);
+ }
+
+ @Test
+ void isNotEqualToWithSameMetadataAndDifferentQualifiers() {
+ OverrideMetadata metadata = createMetadata(field(ConfigA.class, "directQualifier"));
+ OverrideMetadata metadata2 = createMetadata(field(ConfigA.class, "customQualifier"));
+ assertThat(metadata).isNotEqualTo(metadata2);
+ }
+
+ private OverrideMetadata createMetadata(Field field) {
+ return createMetadata(field, null);
+ }
+
+ private OverrideMetadata createMetadata(Field field, @Nullable String name) {
+ return new DummyOverrideMetadata(field, field.getType(), name);
+ }
+
+ private Field field(Class> target, String fieldName) {
+ Field field = ReflectionUtils.findField(target, fieldName);
+ assertThat(field).isNotNull();
+ return field;
+ }
+
+ private Consumer hasTestBeanMetadata(Field field, Class> beanType, @Nullable String beanName) {
+ return hasOverrideMetadata(field, beanType, BeanOverrideStrategy.REPLACE_DEFINITION, beanName);
+ }
+
+ private Consumer hasOverrideMetadata(Field field, Class> beanType, BeanOverrideStrategy strategy, @Nullable String beanName) {
+ return metadata -> {
+ assertThat(metadata.getField()).isEqualTo(field);
+ assertThat(metadata.getBeanType().toClass()).isEqualTo(beanType);
+ assertThat(metadata.getStrategy()).isEqualTo(strategy);
+ assertThat(metadata.getBeanName()).isEqualTo(beanName);
+ };
+ }
+
+
+ static class SingleAnnotation {
+
+ @DummyBean
+ String message;
+
+ }
+
+ static class MultipleAnnotations {
+
+ @DummyBean
+ String message;
+
+ @DummyBean
+ Integer counter;
+ }
+
+ static class MultipleAnnotationsDuplicate {
+
+ @DummyBean
+ String message1;
+
+ @DummyBean
+ String message2;
+
+ }
+
+ static class MultipleAnnotationsOnSameField {
+
+ @MetaDummyBean()
+ @DummyBean
+ String message;
+
+ static String foo() {
+ return "foo";
+ }
+ }
+
+ public static class ConfigA {
+
+ private ExampleService noQualifier;
- @NonNull
- String annotated = "exampleField";
+ @Qualifier("test")
+ private ExampleService directQualifier;
+
+ @Qualifier("different")
+ private ExampleService differentDirectQualifier;
+
+ @CustomQualifier
+ private ExampleService customQualifier;
- private static OverrideMetadata exampleOverride() throws Exception {
- Field field = OverrideMetadataTests.class.getDeclaredField("annotated");
- return new ConcreteOverrideMetadata(field, ResolvableType.forClass(String.class),
- BeanOverrideStrategy.REPLACE_DEFINITION);
}
- static class ConcreteOverrideMetadata extends OverrideMetadata {
+ public static class ConfigB {
+
+ private ExampleService noQualifier;
+
+ @Qualifier("test")
+ private ExampleService directQualifier;
+
+ }
+
+ // Simple OverrideMetadata implementation
+
+ @Target(ElementType.FIELD)
+ @Retention(RetentionPolicy.RUNTIME)
+ @DummyBean
+ public @interface MetaDummyBean {}
- ConcreteOverrideMetadata(Field field, ResolvableType typeToOverride,
- BeanOverrideStrategy strategy) {
+ @Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
+ @Retention(RetentionPolicy.RUNTIME)
+ @Documented
+ @BeanOverride(DummyBeanOverrideProcessor.class)
+ public @interface DummyBean {}
- super(field, typeToOverride, null, strategy);
+ static class DummyBeanOverrideProcessor implements BeanOverrideProcessor {
+
+ @Override
+ public OverrideMetadata createMetadata(Annotation overrideAnnotation, Class> testClass, Field field) {
+ return new DummyOverrideMetadata(field, field.getType(), null);
+ }
+ }
+
+ static class DummyOverrideMetadata extends OverrideMetadata {
+
+ @Nullable
+ private final String beanName;
+
+ DummyOverrideMetadata(Field field, Class> typeToOverride, @Nullable String beanName) {
+ super(field, ResolvableType.forClass(typeToOverride), beanName, BeanOverrideStrategy.REPLACE_DEFINITION);
+ this.beanName = beanName;
+ }
+
+ @Override
+ @Nullable
+ public String getBeanName() {
+ return this.beanName;
}
@Override
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanInheritanceIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanInheritanceIntegrationTests.java
index b02ced0a63d..c6289d6323e 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanInheritanceIntegrationTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanInheritanceIntegrationTests.java
@@ -23,7 +23,6 @@ import org.springframework.test.context.junit.EngineTestKitUtils;
import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure;
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf;
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message;
-import static org.springframework.test.context.junit.EngineTestKitUtils.rootCause;
/**
* {@link TestBean @TestBean} inheritance integration tests for failure scenarios.
@@ -40,12 +39,11 @@ class FailingTestBeanInheritanceIntegrationTests {
Class> testClass = FieldInSupertypeButNoMethodTestCase.class;
EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1,
finishedWithFailure(
- rootCause(
instanceOf(IllegalStateException.class),
message("""
Failed to find a static test bean factory method in %s with return type %s \
whose name matches one of the supported candidates [someBeanTestOverride]"""
- .formatted(testClass.getName(), AbstractTestBeanIntegrationTestCase.Pojo.class.getName())))));
+ .formatted(testClass.getName(), AbstractTestBeanIntegrationTestCase.Pojo.class.getName()))));
}
@Test
@@ -53,13 +51,12 @@ class FailingTestBeanInheritanceIntegrationTests {
Class> testClass = Method1InSupertypeAndMethod2InTypeTestCase.class;
EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1,
finishedWithFailure(
- rootCause(
instanceOf(IllegalStateException.class),
message("""
Found 2 competing static test bean factory methods in %s with return type %s \
whose name matches one of the supported candidates \
[anotherBeanTestOverride, thirdBeanTestOverride]"""
- .formatted(testClass.getName(), AbstractTestBeanIntegrationTestCase.Pojo.class.getName())))));
+ .formatted(testClass.getName(), AbstractTestBeanIntegrationTestCase.Pojo.class.getName()))));
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanIntegrationTests.java
index 9c04f8a34a5..f7958c4f457 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanIntegrationTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanIntegrationTests.java
@@ -69,25 +69,22 @@ class FailingTestBeanIntegrationTests {
Class> testClass = ExplicitTestOverrideMethodNotPresentTestCase.class;
EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1,
finishedWithFailure(
- rootCause(
instanceOf(IllegalStateException.class),
message("""
Failed to find a static test bean factory method in %s with return type \
java.lang.String whose name matches one of the supported candidates \
- [notPresent]""".formatted(testClass.getName())))));
+ [notPresent]""".formatted(testClass.getName()))));
}
@Test
void testBeanFailingNoExplicitMethod() {
Class> testClass = ImplicitTestOverrideMethodNotPresentTestCase.class;
EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1,
- finishedWithFailure(
- rootCause(
- instanceOf(IllegalStateException.class),
+ finishedWithFailure(instanceOf(IllegalStateException.class),
message("""
Failed to find a static test bean factory method in %s with return type \
java.lang.String whose name matches one of the supported candidates \
- [fieldTestOverride]""".formatted(testClass.getName())))));
+ [fieldTestOverride]""".formatted(testClass.getName()))));
}
@Test
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideMetadataTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideMetadataTests.java
new file mode 100644
index 00000000000..d3643181440
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideMetadataTests.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.bean.override.convention;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.ResolvableType;
+import org.springframework.test.context.bean.override.OverrideMetadata;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.StringUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+
+/**
+ * Tests for {@link TestBeanOverrideMetadata}.
+ *
+ * @author Stephane Nicoll
+ */
+class TestBeanOverrideMetadataTests {
+
+ @Test
+ void forTestClassSetsNameToNullIfAnnotationNameIsNull() {
+ List list = OverrideMetadata.forTestClass(SampleOneOverride.class);
+ assertThat(list).singleElement().satisfies(metadata -> assertThat(metadata.getBeanName()).isNull());
+ }
+
+ @Test
+ void forTestClassSetsNameToAnnotationName() {
+ List list = OverrideMetadata.forTestClass(SampleOneOverrideWithName.class);
+ assertThat(list).singleElement().satisfies(metadata -> assertThat(metadata.getBeanName()).isEqualTo("anotherBean"));
+ }
+
+ @Test
+ void forTestClassWithMissingMethod() {
+ assertThatIllegalStateException()
+ .isThrownBy(() ->OverrideMetadata.forTestClass(SampleMissingMethod.class))
+ .withMessageStartingWith("Failed to find a static test bean factory method")
+ .withMessageContaining("messageTestOverride");
+ }
+
+ @Test
+ void isEqualToWithSameInstance() {
+ TestBeanOverrideMetadata metadata = createMetadata(sampleField("message"), sampleMethod("message"));
+ assertThat(metadata).isEqualTo(metadata);
+ assertThat(metadata).hasSameHashCodeAs(metadata);
+ }
+
+ @Test
+ void isEqualToWithSameMetadata() {
+ TestBeanOverrideMetadata metadata1 = createMetadata(sampleField("message"), sampleMethod("message"));
+ TestBeanOverrideMetadata metadata2 = createMetadata(sampleField("message"), sampleMethod("message"));
+ assertThat(metadata1).isEqualTo(metadata2);
+ assertThat(metadata1).hasSameHashCodeAs(metadata2);
+ }
+
+ @Test
+ void isEqualToWithSameMetadataButDifferentField() {
+ TestBeanOverrideMetadata metadata1 = createMetadata(sampleField("message"), sampleMethod("message"));
+ TestBeanOverrideMetadata metadata2 = createMetadata(sampleField("message2"), sampleMethod("message"));
+ assertThat(metadata1).isEqualTo(metadata2);
+ assertThat(metadata1).hasSameHashCodeAs(metadata2);
+ }
+
+ @Test
+ void isNotEqualToWithSameMetadataButDifferentBeanName() {
+ TestBeanOverrideMetadata metadata1 = createMetadata(sampleField("message"), sampleMethod("message"));
+ TestBeanOverrideMetadata metadata2 = createMetadata(sampleField("message3"), sampleMethod("message"));
+ assertThat(metadata1).isNotEqualTo(metadata2);
+ }
+
+ @Test
+ void isNotEqualToWithSameMetadataButDifferentMethod() {
+ TestBeanOverrideMetadata metadata1 = createMetadata(sampleField("message"), sampleMethod("message"));
+ TestBeanOverrideMetadata metadata2 = createMetadata(sampleField("message"), sampleMethod("description"));
+ assertThat(metadata1).isNotEqualTo(metadata2);
+ }
+
+ @Test
+ void isNotEqualToWithSameMetadataButDifferentAnnotations() {
+ TestBeanOverrideMetadata metadata1 = createMetadata(sampleField("message"), sampleMethod("message"));
+ TestBeanOverrideMetadata metadata2 = createMetadata(sampleField("message4"), sampleMethod("message"));
+ assertThat(metadata1).isNotEqualTo(metadata2);
+ }
+
+ private Field sampleField(String fieldName) {
+ Field field = ReflectionUtils.findField(Sample.class, fieldName);
+ assertThat(field).isNotNull();
+ return field;
+ }
+
+ private Method sampleMethod(String noArgMethodName) {
+ Method method = ReflectionUtils.findMethod(Sample.class, noArgMethodName);
+ assertThat(method).isNotNull();
+ return method;
+ }
+
+ private TestBeanOverrideMetadata createMetadata(Field field, Method overrideMethod) {
+ TestBean annotation = field.getAnnotation(TestBean.class);
+ String beanName = (StringUtils.hasText(annotation.value()) ? annotation.value() : null);
+ return new TestBeanOverrideMetadata(field, ResolvableType.forClass(field.getType()), beanName, overrideMethod);
+ }
+
+ static class SampleOneOverride {
+
+ @TestBean(methodName = "message")
+ String message;
+
+ static String message() {
+ return "OK";
+ }
+
+ }
+
+ static class SampleOneOverrideWithName {
+
+ @TestBean(name = "anotherBean", methodName = "message")
+ String message;
+
+ static String message() {
+ return "OK";
+ }
+
+ }
+
+ static class SampleMissingMethod {
+
+ @TestBean
+ String message;
+
+ }
+
+
+ @SuppressWarnings("unused")
+ static class Sample {
+
+ @TestBean
+ private String message;
+
+ @TestBean
+ private String message2;
+
+ @TestBean(name = "anotherBean")
+ private String message3;
+
+ @Qualifier("anotherBean")
+ @TestBean
+ private String message4;
+
+ static String message() {
+ return "OK";
+ }
+
+ static String description() {
+ return message();
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanByTypeIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanByTypeIntegrationTests.java
index 2aa512b24f9..28bd78c05a0 100644
--- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanByTypeIntegrationTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanByTypeIntegrationTests.java
@@ -19,6 +19,7 @@ package org.springframework.test.context.bean.override.mockito;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
+import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -30,7 +31,7 @@ import org.springframework.test.context.bean.override.example.RealExampleService
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatException;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.BDDMockito.when;
import static org.mockito.Mockito.times;
@@ -80,9 +81,9 @@ public class MockitoBeanByTypeIntegrationTests {
.satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()).as("isMock").isTrue())
.isSameAs(ctx.getBean("ambiguous2"));
- assertThatException()
+ assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
.isThrownBy(() -> ctx.getBean(StringBuilder.class))
- .withMessageEndingWith("but found 2: ambiguous2,ambiguous1");
+ .satisfies(ex -> assertThat(ex.getBeanNamesFound()).containsOnly("ambiguous1", "ambiguous2"));
assertThat(this.ambiguous).isEmpty();
assertThat(this.ambiguous.substring(0)).isNull();
@@ -97,9 +98,9 @@ public class MockitoBeanByTypeIntegrationTests {
.satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()).as("isMock").isTrue())
.isSameAs(ctx.getBean("ambiguous1"));
- assertThatException()
+ assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
.isThrownBy(() -> ctx.getBean(StringBuilder.class))
- .withMessageEndingWith("but found 2: ambiguous2,ambiguous1");
+ .satisfies(ex -> assertThat(ex.getBeanNamesFound()).containsOnly("ambiguous1", "ambiguous2"));
assertThat(this.ambiguousMeta).isEmpty();
assertThat(this.ambiguousMeta.substring(0)).isNull();
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideMetadataTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideMetadataTests.java
new file mode 100644
index 00000000000..61b1fd85031
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideMetadataTests.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.bean.override.mockito;
+
+import java.io.Externalizable;
+import java.lang.reflect.Field;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+import org.mockito.Answers;
+
+import org.springframework.core.ResolvableType;
+import org.springframework.test.context.bean.override.OverrideMetadata;
+import org.springframework.util.ReflectionUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link MockitoBeanOverrideMetadata}.
+ *
+ * @author Stephane Nicoll
+ */
+class MockitoBeanOverrideMetadataTests {
+
+ @Test
+ void forTestClassSetsNameToNullIfAnnotationNameIsNull() {
+ List list = OverrideMetadata.forTestClass(SampleOneMock.class);
+ assertThat(list).singleElement().satisfies(metadata -> assertThat(metadata.getBeanName()).isNull());
+ }
+
+ @Test
+ void forTestClassSetsNameToAnnotationName() {
+ List list = OverrideMetadata.forTestClass(SampleOneMockWithName.class);
+ assertThat(list).singleElement().satisfies(metadata -> assertThat(metadata.getBeanName()).isEqualTo("anotherService"));
+ }
+
+ @Test
+ void isEqualToWithSameInstance() {
+ MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service"));
+ assertThat(metadata).isEqualTo(metadata);
+ assertThat(metadata).hasSameHashCodeAs(metadata);
+ }
+
+ @Test
+ void isEqualToWithSameMetadata() {
+ MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service"));
+ MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service"));
+ assertThat(metadata).isEqualTo(metadata2);
+ assertThat(metadata).hasSameHashCodeAs(metadata2);
+ }
+
+ @Test
+ void isEqualToWithSameMetadataButDifferentField() {
+ MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service"));
+ MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service2"));
+ assertThat(metadata).isEqualTo(metadata2);
+ assertThat(metadata).hasSameHashCodeAs(metadata2);
+ }
+
+ @Test
+ void isNotEqualToWithSameMetadataButDifferentBeanName() {
+ MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service"));
+ MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service3"));
+ assertThat(metadata).isNotEqualTo(metadata2);
+ }
+
+ @Test
+ void isNotEqualToWithSameMetadataButDifferentExtraInterfaces() {
+ MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service"));
+ MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service4"));
+ assertThat(metadata).isNotEqualTo(metadata2);
+ }
+
+ @Test
+ void isNotEqualToWithSameMetadataButDifferentAnswers() {
+ MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service"));
+ MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service5"));
+ assertThat(metadata).isNotEqualTo(metadata2);
+ }
+
+ @Test
+ void isNotEqualToWithSameMetadataButDifferentSerializableFlag() {
+ MockitoBeanOverrideMetadata metadata = createMetadata(sampleField("service"));
+ MockitoBeanOverrideMetadata metadata2 = createMetadata(sampleField("service6"));
+ assertThat(metadata).isNotEqualTo(metadata2);
+ }
+
+
+ private Field sampleField(String fieldName) {
+ Field field = ReflectionUtils.findField(Sample.class, fieldName);
+ assertThat(field).isNotNull();
+ return field;
+ }
+
+ private MockitoBeanOverrideMetadata createMetadata(Field field) {
+ MockitoBean annotation = field.getAnnotation(MockitoBean.class);
+ return new MockitoBeanOverrideMetadata(field, ResolvableType.forClass(field.getType()), annotation);
+ }
+
+
+ static class SampleOneMock {
+
+ @MockitoBean
+ String service;
+
+ }
+
+ static class SampleOneMockWithName {
+
+ @MockitoBean(name = "anotherService")
+ String service;
+
+ }
+
+ static class Sample {
+
+ @MockitoBean
+ private String service;
+
+ @MockitoBean
+ private String service2;
+
+ @MockitoBean(name = "beanToMock")
+ private String service3;
+
+ @MockitoBean(extraInterfaces = Externalizable.class)
+ private String service4;
+
+ @MockitoBean(answers = Answers.RETURNS_MOCKS)
+ private String service5;
+
+ @MockitoBean(serializable = true)
+ private String service6;
+
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideMetadataTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideMetadataTests.java
new file mode 100644
index 00000000000..c02e4ebfa93
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideMetadataTests.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.bean.override.mockito;
+
+import java.lang.reflect.Field;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.core.ResolvableType;
+import org.springframework.test.context.bean.override.OverrideMetadata;
+import org.springframework.util.ReflectionUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link MockitoSpyBeanOverrideMetadata}.
+ *
+ * @author Stephane Nicoll
+ */
+class MockitoSpyBeanOverrideMetadataTests {
+
+ @Test
+ void forTestClassSetsNameToNullIfAnnotationNameIsNull() {
+ List list = OverrideMetadata.forTestClass(SampleOneSpy.class);
+ assertThat(list).singleElement().satisfies(metadata -> assertThat(metadata.getBeanName()).isNull());
+ }
+
+ @Test
+ void forTestClassSetsNameToAnnotationName() {
+ List list = OverrideMetadata.forTestClass(SampleOneSpyWithName.class);
+ assertThat(list).singleElement().satisfies(metadata -> assertThat(metadata.getBeanName()).isEqualTo("anotherService"));
+ }
+
+ @Test
+ void isEqualToWithSameInstance() {
+ MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service"));
+ assertThat(metadata).isEqualTo(metadata);
+ assertThat(metadata).hasSameHashCodeAs(metadata);
+ }
+
+ @Test
+ void isEqualToWithSameMetadata() {
+ MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service"));
+ MockitoSpyBeanOverrideMetadata metadata2 = createMetadata(sampleField("service"));
+ assertThat(metadata).isEqualTo(metadata2);
+ assertThat(metadata).hasSameHashCodeAs(metadata2);
+ }
+
+ @Test
+ void isEqualToWithSameMetadataButDifferentField() {
+ MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service"));
+ MockitoSpyBeanOverrideMetadata metadata2 = createMetadata(sampleField("service2"));
+ assertThat(metadata).isEqualTo(metadata2);
+ assertThat(metadata).hasSameHashCodeAs(metadata2);
+ }
+
+ @Test
+ void isNotEqualToWithSameMetadataButDifferentBeanName() {
+ MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service"));
+ MockitoSpyBeanOverrideMetadata metadata2 = createMetadata(sampleField("service3"));
+ assertThat(metadata).isNotEqualTo(metadata2);
+ }
+
+ @Test
+ void isNotEqualToWithSameMetadataButDifferentReset() {
+ MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service"));
+ MockitoSpyBeanOverrideMetadata metadata2 = createMetadata(sampleField("service4"));
+ assertThat(metadata).isNotEqualTo(metadata2);
+ }
+
+ @Test
+ void isNotEqualToWithSameMetadataButDifferentProxyTargetAwareFlag() {
+ MockitoSpyBeanOverrideMetadata metadata = createMetadata(sampleField("service"));
+ MockitoSpyBeanOverrideMetadata metadata2 = createMetadata(sampleField("service5"));
+ assertThat(metadata).isNotEqualTo(metadata2);
+ }
+
+
+ private Field sampleField(String fieldName) {
+ Field field = ReflectionUtils.findField(Sample.class, fieldName);
+ assertThat(field).isNotNull();
+ return field;
+ }
+
+ private MockitoSpyBeanOverrideMetadata createMetadata(Field field) {
+ MockitoSpyBean annotation = field.getAnnotation(MockitoSpyBean.class);
+ return new MockitoSpyBeanOverrideMetadata(field, ResolvableType.forClass(field.getType()), annotation);
+ }
+
+
+ static class SampleOneSpy {
+
+ @MockitoSpyBean
+ String service;
+
+ }
+
+ static class SampleOneSpyWithName {
+
+ @MockitoSpyBean(name = "anotherService")
+ String service;
+
+ }
+
+ static class Sample {
+
+ @MockitoSpyBean
+ private String service;
+
+ @MockitoSpyBean
+ private String service2;
+
+ @MockitoSpyBean(name = "beanToMock")
+ private String service3;
+
+ @MockitoSpyBean(reset = MockReset.BEFORE)
+ private String service4;
+
+ @MockitoSpyBean(proxyTargetAware = false)
+ private String service5;
+
+ }
+
+}