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 af12c936d2..014c2f7082 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 @@ -60,6 +60,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 { @@ -169,8 +170,10 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, } else if (requireExistingBean) { Field field = handler.getField(); - throw new IllegalStateException( - "Unable to replace bean: there is no bean with name '%s' and type %s%s." + throw new IllegalStateException(""" + Unable to replace bean: there is no bean with name '%s' and type %s%s. \ + If the bean is defined in a @Bean method, make sure the return type is the \ + most specific type possible (for example, the concrete implementation type).""" .formatted(beanName, handler.getBeanType(), requiredByField(field))); } // 4) We are creating a bean by-name with the provided beanName. @@ -258,7 +261,11 @@ 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. \ + If the bean is defined in a @Bean method, make sure the return type is the \ + most specific type possible (for example, the concrete implementation type).""" + .formatted(beanType, requiredByField(field)); } else { message += "found %d beans of type %s%s: %s" @@ -272,8 +279,10 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, // We are wrapping an existing bean by-name. Set candidates = getExistingBeanNamesByType(beanFactory, handler, false); if (!candidates.contains(beanName)) { - throw new IllegalStateException( - "Unable to wrap bean: there is no bean with name '%s' and type %s%s." + throw new IllegalStateException(""" + Unable to wrap bean: there is no bean with name '%s' and type %s%s. \ + If the bean is defined in a @Bean method, make sure the return type is the \ + most specific type possible (for example, the concrete implementation type).""" .formatted(beanName, beanType, requiredByField(field))); } } @@ -297,8 +306,10 @@ class BeanOverrideBeanFactoryPostProcessor implements BeanFactoryPostProcessor, int candidateCount = candidateNames.size(); if (candidateCount == 0) { if (requireExistingBean) { - throw new IllegalStateException( - "Unable to override bean: there are no beans of type %s%s." + throw new IllegalStateException(""" + Unable to override bean: there are no beans of type %s%s. \ + If the bean is defined in a @Bean method, make sure the return type is the \ + most specific type possible (for example, the concrete implementation type).""" .formatted(beanType, requiredByField(field))); } return null; 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..76cb72be8b 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 @@ -87,7 +87,9 @@ class BeanOverrideBeanFactoryPostProcessorTests { .isThrownBy(context::refresh) .withMessage(""" Unable to replace bean: there is no bean with name 'descriptionBean' \ - and type java.lang.String (as required by field 'ByNameTestCase.description')."""); + and type java.lang.String (as required by field 'ByNameTestCase.description'). \ + If the bean is defined in a @Bean method, make sure the return type is the most \ + specific type possible (for example, the concrete implementation type)."""); } @Test @@ -99,7 +101,9 @@ class BeanOverrideBeanFactoryPostProcessorTests { .isThrownBy(context::refresh) .withMessage(""" Unable to replace bean: there is no bean with name 'descriptionBean' \ - and type java.lang.String (as required by field 'ByNameTestCase.description')."""); + and type java.lang.String (as required by field 'ByNameTestCase.description'). \ + If the bean is defined in a @Bean method, make sure the return type is the most \ + specific type possible (for example, the concrete implementation type)."""); } @Test @@ -146,7 +150,9 @@ class BeanOverrideBeanFactoryPostProcessorTests { .isThrownBy(context::refresh) .withMessage(""" Unable to override bean: there are no beans of type java.lang.Integer \ - (as required by field 'ByTypeTestCase.counter')."""); + (as required by field 'ByTypeTestCase.counter'). \ + If the bean is defined in a @Bean method, make sure the return type is the most \ + specific type possible (for example, the concrete implementation type)."""); } @Test 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..08600f7c09 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 @@ -42,7 +42,9 @@ public class TestBeanTests { .isThrownBy(context::refresh) .withMessage(""" Unable to replace bean: there is no bean with name 'beanToOverride' \ - and type java.lang.String (as required by field 'FailureByNameLookup.example')."""); + and type java.lang.String (as required by field 'FailureByNameLookup.example'). \ + If the bean is defined in a @Bean method, make sure the return type is the most \ + specific type possible (for example, the concrete implementation type)."""); } @Test @@ -54,7 +56,9 @@ public class TestBeanTests { .isThrownBy(context::refresh) .withMessage(""" Unable to replace bean: there is no bean with name 'beanToOverride' \ - and type java.lang.String (as required by field 'FailureByNameLookup.example')."""); + and type java.lang.String (as required by field 'FailureByNameLookup.example'). \ + If the bean is defined in a @Bean method, make sure the return type is the most \ + specific type possible (for example, the concrete implementation type)."""); } @Test @@ -64,8 +68,9 @@ public class TestBeanTests { assertThatIllegalStateException() .isThrownBy(context::refresh) .withMessage(""" - Unable to override bean: there are no beans of \ - type %s (as required by field '%s.example').""", + Unable to override bean: there are no beans of type %s (as required by field '%s.example'). \ + If the bean is defined in a @Bean method, make sure the return type is the most \ + specific type possible (for example, the concrete implementation type).""", 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..3090ccd10d 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -42,7 +42,9 @@ class MockitoBeanConfigurationErrorTests { .isThrownBy(context::refresh) .withMessage(""" Unable to replace bean: there is no bean with name 'beanToOverride' \ - and type java.lang.String (as required by field 'FailureByNameLookup.example')."""); + and type java.lang.String (as required by field 'FailureByNameLookup.example'). \ + If the bean is defined in a @Bean method, make sure the return type is the most \ + specific type possible (for example, the concrete implementation type)."""); } @Test @@ -54,7 +56,9 @@ class MockitoBeanConfigurationErrorTests { .isThrownBy(context::refresh) .withMessage(""" Unable to replace bean: there is no bean with name 'beanToOverride' \ - and type java.lang.String (as required by field 'FailureByNameLookup.example')."""); + and type java.lang.String (as required by field 'FailureByNameLookup.example'). \ + If the bean is defined in a @Bean method, make sure the return type is the most \ + specific type possible (for example, the concrete implementation type)."""); } @Test @@ -65,7 +69,9 @@ class MockitoBeanConfigurationErrorTests { .isThrownBy(context::refresh) .withMessage(""" Unable to override bean: there are no beans of \ - type java.lang.String (as required by field 'FailureByTypeLookup.example')."""); + type java.lang.String (as required by field 'FailureByTypeLookup.example'). \ + If the bean is defined in a @Bean method, make sure the return type is the most \ + specific type possible (for example, the concrete implementation type)."""); } @Test 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..c1564c1dd3 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -41,7 +41,9 @@ class MockitoSpyBeanConfigurationErrorTests { .isThrownBy(context::refresh) .withMessage(""" Unable to wrap bean: there is no bean with name 'beanToSpy' and \ - type java.lang.String (as required by field 'ByNameSingleLookup.example')."""); + type java.lang.String (as required by field 'ByNameSingleLookup.example'). \ + If the bean is defined in a @Bean method, make sure the return type is the most \ + specific type possible (for example, the concrete implementation type)."""); } @Test @@ -52,7 +54,9 @@ class MockitoSpyBeanConfigurationErrorTests { .isThrownBy(context::refresh) .withMessage(""" Unable to select a bean to wrap: there are no beans of type java.lang.String \ - (as required by field 'ByTypeSingleLookup.example')."""); + (as required by field 'ByTypeSingleLookup.example'). \ + If the bean is defined in a @Bean method, make sure the return type is the most \ + specific type possible (for example, the concrete implementation type)."""); } @Test