Allow MockitoBean to create a mock for a bean that does not exist

This commit aligns the by-type lookup for bean overrides with what was
done when a bean name is present. It now correctly generate a bean
name rather than failing because no bean of that type exists.

Closes gh-32990
This commit is contained in:
Stéphane Nicoll
2024-06-10 09:53:27 +02:00
parent bc98410acf
commit 536de9ac80
4 changed files with 54 additions and 43 deletions

View File

@@ -34,6 +34,8 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
@@ -63,6 +65,8 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
private final BeanOverrideRegistrar overrideRegistrar;
private final BeanNameGenerator beanNameGenerator = new DefaultBeanNameGenerator();
/**
* Create a new {@code BeanOverrideBeanFactoryPostProcessor} instance with
@@ -133,18 +137,11 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
String beanNameIncludingFactory;
BeanDefinition existingBeanDefinition = null;
if (beanName == null) {
Set<String> candidateNames = getExistingBeanNamesByType(beanFactory, overrideMetadata, true);
int candidateCount = candidateNames.size();
if (candidateCount != 1) {
Field field = overrideMetadata.getField();
throw new IllegalStateException("Unable to select a bean definition to override: found " +
candidateCount + " bean definitions of type " + overrideMetadata.getBeanType() +
" (as required by annotated field '" + field.getDeclaringClass().getSimpleName() +
"." + field.getName() + "')" + (candidateCount > 0 ? ": " + candidateNames : ""));
}
beanNameIncludingFactory = candidateNames.iterator().next();
beanNameIncludingFactory = getBeanNameForType(beanFactory, registry, overrideMetadata, beanDefinition, enforceExistingDefinition);
beanName = BeanFactoryUtils.transformedBeanName(beanNameIncludingFactory);
existingBeanDefinition = beanFactory.getBeanDefinition(beanName);
if (registry.containsBeanDefinition(beanName)) {
existingBeanDefinition = beanFactory.getBeanDefinition(beanName);
}
}
else {
Set<String> candidates = getExistingBeanNamesByType(beanFactory, overrideMetadata, false);
@@ -176,6 +173,30 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
this.overrideRegistrar.registerNameForMetadata(overrideMetadata, beanNameIncludingFactory);
}
private String getBeanNameForType(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry,
OverrideMetadata overrideMetadata, RootBeanDefinition beanDefinition, boolean enforceExistingDefinition) {
Set<String> candidateNames = getExistingBeanNamesByType(beanFactory, overrideMetadata, true);
int candidateCount = candidateNames.size();
if (candidateCount == 1) {
return candidateNames.iterator().next();
}
else if (candidateCount == 0) {
if (enforceExistingDefinition) {
Field field = overrideMetadata.getField();
throw new IllegalStateException(
"Unable to override bean: no bean definitions of type %s (as required by annotated field '%s.%s')"
.formatted(overrideMetadata.getBeanType(), field.getDeclaringClass().getSimpleName(), field.getName()));
}
return this.beanNameGenerator.generateBeanName(beanDefinition, registry);
}
Field field = overrideMetadata.getField();
throw new IllegalStateException(String.format(
"Unable to select a bean definition to override: found %s bean definitions of type %s " +
"(as required by annotated field '%s.%s'): %s",
candidateCount, overrideMetadata.getBeanType(), field.getDeclaringClass().getSimpleName(),
field.getName(), candidateNames));
}
/**
* Check that the expected bean name is registered and matches the type to override.
* <p>If so, put the override metadata in the early tracking map.
@@ -209,7 +230,7 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor,
}
RootBeanDefinition createBeanDefinition(OverrideMetadata metadata) {
RootBeanDefinition definition = new RootBeanDefinition();
RootBeanDefinition definition = new RootBeanDefinition(metadata.getBeanType().resolve());
definition.setTargetType(metadata.getBeanType());
definition.setQualifiedElement(metadata.getField());
return definition;