Browse Source

Review TCF cache support for bean override

This commit reviews how bean override support can influence the key of
an application context cached by the TCF. OverrideMetadata and its
subclasses now implement a proper equals/hashCode pair that is tested
in various scenarios.

Due to how the TCF operates, OverrideMetadata has to be computed in
two locations:

1. In a ContextCustomizerFactory, using the metadata in the enclosing
class if any. This determines whether a customizer is needed in the
first place. The computed set of unique metadata identifies the
customizer and participates in the application context cache's key.
2. In the TestExecutionListener so that it knows the override points
it has to process.

Parsing of the metadata based on a test class has been greatly
simplified and moved to OverrideMetadata proper as we don't need several
flavors. 1 and 2 are using the same algorithm with the former wrapping
that in a Set to compute a proper key.

BeanOverrideContextCustomizerEqualityTests provides a framework for
testing edge cases as we only care about whether the created
ContextCustomizer behaves correctly against the identity of another.

Closes gh-32884
pull/32992/head
Stéphane Nicoll 2 years ago
parent
commit
7916f74942
  1. 37
      spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessor.java
  2. 32
      spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizer.java
  3. 24
      spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactory.java
  4. 101
      spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideParsingUtils.java
  5. 19
      spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideRegistrar.java
  6. 13
      spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideTestExecutionListener.java
  7. 47
      spring-test/src/main/java/org/springframework/test/context/bean/override/OverrideMetadata.java
  8. 2
      spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideMetadata.java
  9. 3
      spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideMetadata.java
  10. 3
      spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoOverrideMetadata.java
  11. 8
      spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideMetadata.java
  12. 4
      spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideBeanFactoryPostProcessorTests.java
  13. 168
      spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerEqualityTests.java
  14. 144
      spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactoryTests.java
  15. 103
      spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerTests.java
  16. 118
      spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideParsingUtilsTests.java
  17. 249
      spring-test/src/test/java/org/springframework/test/context/bean/override/OverrideMetadataTests.java
  18. 7
      spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanInheritanceIntegrationTests.java
  19. 9
      spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanIntegrationTests.java
  20. 178
      spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideMetadataTests.java
  21. 11
      spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanByTypeIntegrationTests.java
  22. 151
      spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideMetadataTests.java
  23. 139
      spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideMetadataTests.java

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

@ -41,38 +41,41 @@ import org.springframework.core.ResolvableType; @@ -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.
*
* <p>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.
* <p>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.
*
* <p>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}.
* <p>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<OverrideMetadata> 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<OverrideMetadata> metadata,
BeanOverrideRegistrar overrideRegistrar) {
this.metadata = metadata;
this.overrideRegistrar = overrideRegistrar;
}
@ -92,7 +95,7 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, @@ -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);
}
}

32
spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizer.java

@ -49,10 +49,10 @@ class BeanOverrideContextCustomizer implements ContextCustomizer { @@ -49,10 +49,10 @@ class BeanOverrideContextCustomizer implements ContextCustomizer {
"org.springframework.test.context.bean.override.internalWrapEarlyBeanPostProcessor";
private final Set<Class<?>> detectedClasses;
private final Set<OverrideMetadata> metadata;
BeanOverrideContextCustomizer(Set<Class<?>> detectedClasses) {
this.detectedClasses = detectedClasses;
BeanOverrideContextCustomizer(Set<OverrideMetadata> metadata) {
this.metadata = metadata;
}
@Override
@ -61,19 +61,25 @@ class BeanOverrideContextCustomizer implements ContextCustomizer { @@ -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<Class<?>> detectedClasses) {
Set<OverrideMetadata> 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 { @@ -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();
}
}

24
spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactory.java

@ -16,13 +16,12 @@ @@ -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; @@ -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 { @@ -38,24 +38,22 @@ class BeanOverrideContextCustomizerFactory implements ContextCustomizerFactory {
@Override
@Nullable
public ContextCustomizer createContextCustomizer(Class<?> testClass,
public BeanOverrideContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
Set<Class<?>> detectedClasses = new LinkedHashSet<>();
findClassesWithBeanOverride(testClass, detectedClasses);
if (detectedClasses.isEmpty()) {
Set<OverrideMetadata> 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<Class<?>> detectedClasses) {
if (BeanOverrideParsingUtils.hasBeanOverride(testClass)) {
detectedClasses.add(testClass);
}
private void findOverrideMetadata(Class<?> testClass, Set<OverrideMetadata> metadata) {
List<OverrideMetadata> overrideMetadata = OverrideMetadata.forTestClass(testClass);
metadata.addAll(overrideMetadata);
if (TestContextAnnotationUtils.searchEnclosingClass(testClass)) {
findClassesWithBeanOverride(testClass.getEnclosingClass(), detectedClasses);
findOverrideMetadata(testClass.getEnclosingClass(), metadata);
}
}

101
spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideParsingUtils.java

@ -1,101 +0,0 @@ @@ -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<OverrideMetadata> parse(Iterable<Class<?>> classes) {
Set<OverrideMetadata> 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<OverrideMetadata> parse(Class<?> clazz) {
return parse(List.of(clazz));
}
private static void parseField(Field field, Class<?> testClass, Set<OverrideMetadata> 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);
});
}
}

19
spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideRegistrar.java

@ -19,7 +19,6 @@ package org.springframework.test.context.bean.override; @@ -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 { @@ -45,21 +44,10 @@ class BeanOverrideRegistrar implements BeanFactoryAware {
private final Map<String, OverrideMetadata> earlyOverrideMetadata = new HashMap<>();
private final Set<OverrideMetadata> 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<Class<?>> 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 { @@ -69,13 +57,6 @@ class BeanOverrideRegistrar implements BeanFactoryAware {
this.beanFactory = cbf;
}
/**
* Get the detected {@link OverrideMetadata} instances.
*/
Set<OverrideMetadata> getOverrideMetadata() {
return this.overrideMetadata;
}
/**
* Check {@link #markWrapEarly(OverrideMetadata, String) early override}
* records and use the {@link OverrideMetadata} to create an override

13
spring-test/src/main/java/org/springframework/test/context/bean/override/BeanOverrideTestExecutionListener.java

@ -17,6 +17,7 @@ @@ -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 @@ -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 @@ -90,13 +91,11 @@ public class BeanOverrideTestExecutionListener extends AbstractTestExecutionList
Class<?> testClass = testContext.getTestClass();
Object testInstance = testContext.getTestInstance();
if (BeanOverrideParsingUtils.hasBeanOverride(testClass)) {
List<OverrideMetadata> 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);
}
}

47
spring-test/src/main/java/org/springframework/test/context/bean/override/OverrideMetadata.java

@ -16,14 +16,26 @@ @@ -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 { @@ -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<OverrideMetadata> forTestClass(Class<?> testClass) {
List<OverrideMetadata> all = new LinkedList<>();
ReflectionUtils.doWithFields(testClass, field -> parseField(field, testClass, all));
return all;
}
private static void parseField(Field field, Class<?> testClass, List<OverrideMetadata> 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 { @@ -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

2
spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideMetadata.java

@ -79,6 +79,6 @@ final class TestBeanOverrideMetadata extends OverrideMetadata { @@ -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();
}
}

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

@ -43,6 +43,7 @@ import static org.mockito.Mockito.mock; @@ -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 { @@ -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

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

@ -31,6 +31,7 @@ import org.springframework.util.ObjectUtils; @@ -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 { @@ -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();
}
}

8
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; @@ -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; @@ -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; @@ -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 { @@ -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

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

@ -17,6 +17,7 @@ @@ -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 { @@ -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<OverrideMetadata> metadata = new LinkedHashSet<>(OverrideMetadata.forTestClass(testClass));
new BeanOverrideContextCustomizer(metadata).customizeContext(context, mock(MergedContextConfiguration.class));
return context;
}

168
spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerEqualityTests.java

@ -0,0 +1,168 @@ @@ -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;
}
}

144
spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerFactoryTests.java

@ -0,0 +1,144 @@ @@ -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<OverrideMetadata> testMetadata(@Nullable String beanName, Class<?> beanType) {
return overrideMetadata(beanName, beanType, BeanOverrideStrategy.REPLACE_DEFINITION);
}
private Consumer<OverrideMetadata> 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";
}
}
}

103
spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideContextCustomizerTests.java

@ -0,0 +1,103 @@ @@ -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();
}
}
}

118
spring-test/src/test/java/org/springframework/test/context/bean/override/BeanOverrideParsingUtilsTests.java

@ -1,118 +0,0 @@ @@ -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;
}
}

249
spring-test/src/test/java/org/springframework/test/context/bean/override/OverrideMetadataTests.java

@ -16,47 +16,266 @@ @@ -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 = OverrideMetadata.forTestClass(SingleAnnotation.class);
assertThat(overrideMetadata).singleElement().satisfies(hasTestBeanMetadata(
field(SingleAnnotation.class, "message"), String.class, null));
}
@Test
void forTestClassWithMultipleFields() {
List<OverrideMetadata> 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 = 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<OverrideMetadata> hasTestBeanMetadata(Field field, Class<?> beanType, @Nullable String beanName) {
return hasOverrideMetadata(field, beanType, BeanOverrideStrategy.REPLACE_DEFINITION, beanName);
}
private Consumer<OverrideMetadata> 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

7
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; @@ -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 { @@ -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 { @@ -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()))));
}

9
spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanIntegrationTests.java

@ -69,25 +69,22 @@ class FailingTestBeanIntegrationTests { @@ -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

178
spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideMetadataTests.java

@ -0,0 +1,178 @@ @@ -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<OverrideMetadata> list = OverrideMetadata.forTestClass(SampleOneOverride.class);
assertThat(list).singleElement().satisfies(metadata -> assertThat(metadata.getBeanName()).isNull());
}
@Test
void forTestClassSetsNameToAnnotationName() {
List<OverrideMetadata> 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();
}
}
}

11
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; @@ -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 @@ -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 { @@ -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 { @@ -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();

151
spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanOverrideMetadataTests.java

@ -0,0 +1,151 @@ @@ -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<OverrideMetadata> list = OverrideMetadata.forTestClass(SampleOneMock.class);
assertThat(list).singleElement().satisfies(metadata -> assertThat(metadata.getBeanName()).isNull());
}
@Test
void forTestClassSetsNameToAnnotationName() {
List<OverrideMetadata> 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;
}
}

139
spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideMetadataTests.java

@ -0,0 +1,139 @@ @@ -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<OverrideMetadata> list = OverrideMetadata.forTestClass(SampleOneSpy.class);
assertThat(list).singleElement().satisfies(metadata -> assertThat(metadata.getBeanName()).isNull());
}
@Test
void forTestClassSetsNameToAnnotationName() {
List<OverrideMetadata> 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;
}
}
Loading…
Cancel
Save