From cf46f391d74977d4e0dca300c2046d2279bd1db9 Mon Sep 17 00:00:00 2001 From: Yanming Zhou Date: Tue, 26 Nov 2024 15:04:37 +0800 Subject: [PATCH] Improve diagnostics when a Bean Override cannot be selected by type See gh-34004 Closes gh-34006 Signed-off-by: Yanming Zhou --- .../BeanOverrideBeanFactoryPostProcessor.java | 14 +++++++++++--- .../BeanOverrideBeanFactoryPostProcessorTests.java | 6 +++--- .../bean/override/convention/TestBeanTests.java | 6 +++--- .../MockitoBeanConfigurationErrorTests.java | 6 +++--- .../MockitoSpyBeanConfigurationErrorTests.java | 2 +- 5 files changed, 21 insertions(+), 13 deletions(-) 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 1a01295a17..fa8aefec26 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 @@ -59,6 +59,7 @@ import org.springframework.util.Assert; * @author Simon Baslé * @author Stephane Nicoll * @author Sam Brannen + * @author Yanming Zhou * @since 6.2 */ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered { @@ -67,6 +68,9 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, private static final BeanNameGenerator beanNameGenerator = DefaultBeanNameGenerator.INSTANCE; + private static final String unableToOverrideByTypeDiagnosticsMessage = " If the bean is defined from a @Bean method," + + " please make sure the return type is the most specific type (recommended) or type can be assigned to %s"; + private final Set beanOverrideHandlers; private final BeanOverrideRegistry beanOverrideRegistry; @@ -170,7 +174,7 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Field field = handler.getField(); throw new IllegalStateException( "Unable to replace bean: there is no bean with name '%s' and type %s%s." - .formatted(beanName, handler.getBeanType(), requiredByField(field))); + .formatted(beanName, handler.getBeanType(), requiredByField(field, handler.getBeanType()))); } // 4) We are creating a bean by-name with the provided beanName. } @@ -257,7 +261,7 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, String message = "Unable to select a bean to wrap: "; int candidateCount = candidateNames.size(); if (candidateCount == 0) { - message += "there are no beans of type %s%s.".formatted(beanType, requiredByField(field)); + message += "there are no beans of type %s%s.".formatted(beanType, requiredByField(field, beanType)); } else { message += "found %d beans of type %s%s: %s" @@ -299,7 +303,7 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, if (requireExistingBean) { throw new IllegalStateException( "Unable to override bean: there are no beans of type %s%s." - .formatted(beanType, requiredByField(field))); + .formatted(beanType, requiredByField(field, beanType))); } return null; } @@ -483,4 +487,8 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, field.getDeclaringClass().getSimpleName(), field.getName()); } + private static String requiredByField(@Nullable Field field, ResolvableType requiredBeanType) { + return requiredByField(field) + '.' + unableToOverrideByTypeDiagnosticsMessage.formatted(requiredBeanType); + } + } 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 8b3ce3c1a6..affcd21db7 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 @@ -85,7 +85,7 @@ class BeanOverrideBeanFactoryPostProcessorTests { assertThatIllegalStateException() .isThrownBy(context::refresh) - .withMessage(""" + .withMessageStartingWith(""" Unable to replace bean: there is no bean with name 'descriptionBean' \ and type java.lang.String (as required by field 'ByNameTestCase.description')."""); } @@ -97,7 +97,7 @@ class BeanOverrideBeanFactoryPostProcessorTests { assertThatIllegalStateException() .isThrownBy(context::refresh) - .withMessage(""" + .withMessageStartingWith(""" Unable to replace bean: there is no bean with name 'descriptionBean' \ and type java.lang.String (as required by field 'ByNameTestCase.description')."""); } @@ -144,7 +144,7 @@ class BeanOverrideBeanFactoryPostProcessorTests { assertThatIllegalStateException() .isThrownBy(context::refresh) - .withMessage(""" + .withMessageStartingWith(""" Unable to override bean: there are no beans of type java.lang.Integer \ (as required by field 'ByTypeTestCase.counter')."""); } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanTests.java index d771af6333..a036872775 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanTests.java @@ -40,7 +40,7 @@ public class TestBeanTests { BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByNameLookup.class, context); assertThatIllegalStateException() .isThrownBy(context::refresh) - .withMessage(""" + .withMessageStartingWith(""" Unable to replace bean: there is no bean with name 'beanToOverride' \ and type java.lang.String (as required by field 'FailureByNameLookup.example')."""); } @@ -52,7 +52,7 @@ public class TestBeanTests { BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByNameLookup.class, context); assertThatIllegalStateException() .isThrownBy(context::refresh) - .withMessage(""" + .withMessageStartingWith(""" Unable to replace bean: there is no bean with name 'beanToOverride' \ and type java.lang.String (as required by field 'FailureByNameLookup.example')."""); } @@ -63,7 +63,7 @@ public class TestBeanTests { BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByTypeLookup.class, context); assertThatIllegalStateException() .isThrownBy(context::refresh) - .withMessage(""" + .withMessageStartingWith(""" Unable to override bean: there are no beans of \ type %s (as required by field '%s.example').""", String.class.getName(), FailureByTypeLookup.class.getSimpleName()); diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanConfigurationErrorTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanConfigurationErrorTests.java index 1182a8afc5..07a06927b4 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanConfigurationErrorTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanConfigurationErrorTests.java @@ -40,7 +40,7 @@ class MockitoBeanConfigurationErrorTests { BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByNameLookup.class, context); assertThatIllegalStateException() .isThrownBy(context::refresh) - .withMessage(""" + .withMessageStartingWith(""" Unable to replace bean: there is no bean with name 'beanToOverride' \ and type java.lang.String (as required by field 'FailureByNameLookup.example')."""); } @@ -52,7 +52,7 @@ class MockitoBeanConfigurationErrorTests { BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByNameLookup.class, context); assertThatIllegalStateException() .isThrownBy(context::refresh) - .withMessage(""" + .withMessageStartingWith(""" Unable to replace bean: there is no bean with name 'beanToOverride' \ and type java.lang.String (as required by field 'FailureByNameLookup.example')."""); } @@ -63,7 +63,7 @@ class MockitoBeanConfigurationErrorTests { BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByTypeLookup.class, context); assertThatIllegalStateException() .isThrownBy(context::refresh) - .withMessage(""" + .withMessageStartingWith(""" Unable to override bean: there are no beans of \ type java.lang.String (as required by field 'FailureByTypeLookup.example')."""); } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanConfigurationErrorTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanConfigurationErrorTests.java index efdbf88733..23b42908a7 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanConfigurationErrorTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanConfigurationErrorTests.java @@ -50,7 +50,7 @@ class MockitoSpyBeanConfigurationErrorTests { BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(ByTypeSingleLookup.class, context); assertThatIllegalStateException() .isThrownBy(context::refresh) - .withMessage(""" + .withMessageStartingWith(""" Unable to select a bean to wrap: there are no beans of type java.lang.String \ (as required by field 'ByTypeSingleLookup.example')."""); }