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 90229ad788..1801081eda 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 @@ -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 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 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 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. *

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; diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanByTypeIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanByTypeIntegrationTests.java index c3418ea9a2..73769e21c8 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanByTypeIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/FailingTestBeanByTypeIntegrationTests.java @@ -51,8 +51,8 @@ class FailingTestBeanByTypeIntegrationTests { cause( instanceOf(IllegalStateException.class), message(""" - Unable to select a bean definition to override: found 0 bean definitions \ - of type %s (as required by annotated field '%s.example')""" + Unable to override bean: no bean definitions of type \ + %s (as required by annotated field '%s.example')""" .formatted(ExampleService.class.getName(), testClass.getSimpleName()))))); } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/FailingMockitoBeanByTypeIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/FailingMockitoBeanByTypeIntegrationTests.java index 62a490609e..bc877302dc 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/FailingMockitoBeanByTypeIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/FailingMockitoBeanByTypeIntegrationTests.java @@ -43,19 +43,6 @@ import static org.junit.platform.testkit.engine.TestExecutionResultConditions.me */ class FailingMockitoBeanByTypeIntegrationTests { - @Test - void zeroCandidates() { - Class testClass = ZeroCandidatesTestCase.class; - EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1, - finishedWithFailure( - cause( - instanceOf(IllegalStateException.class), - message(""" - Unable to select a bean definition to override: found 0 bean definitions \ - of type %s (as required by annotated field '%s.example')""" - .formatted(ExampleService.class.getName(), testClass.getSimpleName()))))); - } - @Test void tooManyCandidates() { Class testClass = TooManyCandidatesTestCase.class; @@ -70,22 +57,6 @@ class FailingMockitoBeanByTypeIntegrationTests { } - @SpringJUnitConfig - static class ZeroCandidatesTestCase { - - @MockitoBean - ExampleService example; - - @Test - void test() { - assertThat(example).isNotNull(); - } - - @Configuration - static class Config { - } - } - @SpringJUnitConfig static class TooManyCandidatesTestCase { 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 28bd78c05a..92be96e444 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 @@ -49,6 +49,9 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; @SpringJUnitConfig public class MockitoBeanByTypeIntegrationTests { + @MockitoBean + AnotherService serviceIsNotABean; + @MockitoBean ExampleService anyNameForService; @@ -60,6 +63,17 @@ public class MockitoBeanByTypeIntegrationTests { @CustomQualifier StringBuilder ambiguousMeta; + @Test + void mockIsCreatedWhenNoCandidateIsFound() { + assertThat(this.serviceIsNotABean) + .satisfies(o -> assertThat(Mockito.mockingDetails(o).isMock()).as("isMock").isTrue()); + + when(this.serviceIsNotABean.hello()).thenReturn("Mocked hello"); + + assertThat(this.serviceIsNotABean.hello()).isEqualTo("Mocked hello"); + verify(this.serviceIsNotABean, times(1)).hello(); + verifyNoMoreInteractions(this.serviceIsNotABean); + } @Test void overrideIsFoundByType(ApplicationContext ctx) { @@ -109,6 +123,11 @@ public class MockitoBeanByTypeIntegrationTests { verifyNoMoreInteractions(this.ambiguousMeta); } + interface AnotherService { + + String hello(); + + } @Configuration static class Config {