diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-mockitobean.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-mockitobean.adoc index 483ca3b2d0..818b8cc230 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-mockitobean.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-mockitobean.adoc @@ -2,7 +2,7 @@ = `@MockitoBean` and `@MockitoSpyBean` `@MockitoBean` and `@MockitoSpyBean` are used on fields in test classes to override beans -in the test's `ApplicationContext` with a Mockito mock or spy, respectively. In the +in the test's `ApplicationContext` with a Mockito _mock_ or _spy_, respectively. In the latter case, the original bean definition is not replaced, but instead an early instance of the bean is captured and wrapped by the spy. @@ -11,9 +11,10 @@ to override. If multiple candidates match, `@Qualifier` can be provided to narro candidate to override. Alternatively, a candidate whose bean definition name matches the name of the field will match. -When using `@MockitoBean`, if you would like for a new bean definition to be created when -a corresponding bean definition does not exist, set the `enforceOverride` attribute to -`false` – for example, `@MockitoBean(enforceOverride = false)`. +When using `@MockitoBean`, a new bean definition will be created if a corresponding bean +definition does not exist. However, if you would like for the test to fail when a +corresponding bean definition does not exist, you can set the `enforceOverride` attribute +to `true` – for example, `@MockitoBean(enforceOverride = true)`. To use a by-name override rather than a by-type override, specify the `name` attribute of the annotation. diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testbean.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testbean.adoc index a384ecbcb6..9ef336315d 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testbean.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testbean.adoc @@ -15,9 +15,10 @@ to override. If multiple candidates match, `@Qualifier` can be provided to narro candidate to override. Alternatively, a candidate whose bean definition name matches the name of the field will match. -If you would like for a new bean definition to be created when a corresponding bean -definition does not exist, set the `enforceOverride` attribute to `false` – for example, -`@TestBean(enforceOverride = false)`. +A new bean definition will be created if a corresponding bean definition does not exist. +However, if you would like for the test to fail when a corresponding bean definition does +not exist, you can set the `enforceOverride` attribute to `true` – for example, +`@TestBean(enforceOverride = true)`. To use a by-name override rather than a by-type override, specify the `name` attribute of the annotation. diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBean.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBean.java index 051199b32c..0bd3d72f3e 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBean.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBean.java @@ -31,16 +31,17 @@ import org.springframework.test.context.bean.override.BeanOverride; * {@link org.springframework.context.ApplicationContext ApplicationContext} * using a static factory method. * - *
By default, the bean to override is inferred from the type of the - * annotated field. This requires that exactly one matching bean definition is - * present in the application context. A {@code @Qualifier} annotation can be + *
By default, the bean to override is inferred from the type of the annotated + * field. If multiple candidates exist, a {@code @Qualifier} annotation can be * used to help disambiguate. In the absence of a {@code @Qualifier} annotation, - * the name of the annotated field will be used as a qualifier. Alternatively, - * you can explicitly specify a bean name to replace by setting the - * {@link #value() value} or {@link #name() name} attribute. If you would like - * for a new bean definition to be created when a corresponding bean definition - * does not exist, set the {@link #enforceOverride() enforceOverride} attribute - * to {@code false}. + * the name of the annotated field will be used as a fallback qualifier. + * Alternatively, you can explicitly specify a bean name to replace by setting the + * {@link #value() value} or {@link #name() name} attribute. + * + *
A new bean definition will be created if a corresponding bean definition does + * not exist. However, if you would like for the test to fail when a corresponding + * bean definition does not exist, you can set the {@link #enforceOverride() + * enforceOverride} attribute to {@code true}. * *
The instance is created from a zero-argument static factory method in the * test class whose return type is compatible with the annotated field. In the @@ -149,13 +150,13 @@ public @interface TestBean { /** * Whether to require the existence of a bean definition for the bean being * overridden. - *
Defaults to {@code true} which means that an exception will be thrown - * if a corresponding bean definition does not exist. - *
Set to {@code false} to create a new bean definition when a corresponding + *
Defaults to {@code false} which means that a new bean definition will + * be created if a corresponding bean definition does not exist. + *
Set to {@code true} to cause an exception to be thrown if a corresponding * bean definition does not exist. - * @see org.springframework.test.context.bean.override.BeanOverrideStrategy#REPLACE_DEFINITION * @see org.springframework.test.context.bean.override.BeanOverrideStrategy#REPLACE_OR_CREATE_DEFINITION + * @see org.springframework.test.context.bean.override.BeanOverrideStrategy#REPLACE_DEFINITION */ - boolean enforceOverride() default true; + boolean enforceOverride() default false; } diff --git a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBean.java b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBean.java index 68b27b6bc6..76162a7c2c 100644 --- a/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBean.java +++ b/spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoBean.java @@ -33,14 +33,17 @@ import org.springframework.test.context.bean.override.BeanOverride; * {@link org.springframework.context.ApplicationContext ApplicationContext} * using a Mockito mock. * - *
If no explicit {@link #name() name} is specified, a target bean definition - * is selected according to the type of the annotated field, and there must be - * exactly one such candidate definition in the context. Otherwise, a {@code @Qualifier} - * annotation can be used to help disambiguate between multiple candidates. If a - * {@link #name() name} is specified, by default a corresponding bean definition - * must exist in the application context. If you would like for a new bean definition - * to be created when a corresponding bean definition does not exist, set the - * {@link #enforceOverride() enforceOverride} attribute to {@code false}. + *
By default, the bean to override is inferred from the type of the annotated + * field. If multiple candidates exist, a {@code @Qualifier} annotation can be + * used to help disambiguate. In the absence of a {@code @Qualifier} annotation, + * the name of the annotated field will be used as a fallback qualifier. + * Alternatively, you can explicitly specify a bean name to replace by setting the + * {@link #name() name} attribute. + * + *
A new bean definition will be created if a corresponding bean definition does + * not exist. However, if you would like for the test to fail when a corresponding + * bean definition does not exist, you can set the {@link #enforceOverride() + * enforceOverride} attribute to {@code true}. * *
Dependencies that are known to the application context but are not beans * (such as those @@ -105,13 +108,13 @@ public @interface MockitoBean { /** * Whether to require the existence of a bean definition for the bean being * overridden. - *
Defaults to {@code true} which means that an exception will be thrown - * if a corresponding bean definition does not exist. - *
Set to {@code false} to create a new bean definition when a corresponding + *
Defaults to {@code false} which means that a new bean definition will + * be created if a corresponding bean definition does not exist. + *
Set to {@code true} to cause an exception to be thrown if a corresponding * bean definition does not exist. - * @see org.springframework.test.context.bean.override.BeanOverrideStrategy#REPLACE_DEFINITION * @see org.springframework.test.context.bean.override.BeanOverrideStrategy#REPLACE_OR_CREATE_DEFINITION + * @see org.springframework.test.context.bean.override.BeanOverrideStrategy#REPLACE_DEFINITION */ - boolean enforceOverride() default true; + boolean enforceOverride() default false; } diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/bean/override/MockitoBeanJupiterTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/bean/override/MockitoBeanJupiterTests.java index 9cd784a775..f914c84c5d 100644 --- a/spring-test/src/test/java/org/springframework/test/context/aot/samples/bean/override/MockitoBeanJupiterTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/bean/override/MockitoBeanJupiterTests.java @@ -40,7 +40,7 @@ public class MockitoBeanJupiterTests { /** * Mock for nonexistent bean. */ - @MockitoBean(enforceOverride = false) + @MockitoBean GreetingService greetingService; /** 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 8e2a1f1c20..d2c2fbcd71 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 @@ -420,7 +420,7 @@ class BeanOverrideBeanFactoryPostProcessorTests { static class CaseByNameWithQualifier { @Qualifier("preferThis") - @TestBean(name = "descriptionBean", enforceOverride = false) + @TestBean(name = "descriptionBean") private String description; static String descriptionBean() { diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanForByTypeLookupIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanForByTypeLookupIntegrationTests.java index 6f7e170ac8..d410c8a280 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanForByTypeLookupIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanForByTypeLookupIntegrationTests.java @@ -39,7 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat; @SpringJUnitConfig public class TestBeanForByTypeLookupIntegrationTests { - @TestBean(enforceOverride = false) + @TestBean MessageService messageService; @TestBean 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 781dedf927..314ba52b99 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 @@ -126,7 +126,7 @@ public class TestBeanTests { static class FailureByTypeLookup { - @TestBean + @TestBean(enforceOverride = true) private String example; static String example() { @@ -136,7 +136,7 @@ public class TestBeanTests { static class FailureByNameLookup { - @TestBean(name = "beanToOverride") + @TestBean(name = "beanToOverride", enforceOverride = true) private String example; static String example() { diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanForByNameLookupIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanForByNameLookupIntegrationTests.java index 0b0e4b5eed..d2d2253096 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanForByNameLookupIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanForByNameLookupIntegrationTests.java @@ -48,10 +48,10 @@ public class MockitoBeanForByNameLookupIntegrationTests { @MockitoBean(name = "nestedField") ExampleService renamed2; - @MockitoBean(name = "nonExistingBean", enforceOverride = false) + @MockitoBean(name = "nonExistingBean") ExampleService nonExisting1; - @MockitoBean(name = "nestedNonExistingBean", enforceOverride = false) + @MockitoBean(name = "nestedNonExistingBean") ExampleService nonExisting2; diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanForByTypeLookupIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanForByTypeLookupIntegrationTests.java index 3cf6685811..0836b45718 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanForByTypeLookupIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanForByTypeLookupIntegrationTests.java @@ -48,10 +48,10 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; @SpringJUnitConfig public class MockitoBeanForByTypeLookupIntegrationTests { - @MockitoBean(enforceOverride = false) + @MockitoBean AnotherService serviceIsNotABean; - @MockitoBean(enforceOverride = false) + @MockitoBean ExampleService anyNameForService; @MockitoBean diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanSettingsStrictIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanSettingsStrictIntegrationTests.java index 583c7d4314..a54fbf3756 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanSettingsStrictIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoBeanSettingsStrictIntegrationTests.java @@ -93,7 +93,7 @@ class MockitoBeanSettingsStrictIntegrationTests { @DirtiesContext static class ImplicitStrictnessWithMockitoBean extends BaseCase { - @MockitoBean(enforceOverride = false) + @MockitoBean @SuppressWarnings("unused") DateTimeFormatter ignoredMock; } diff --git a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoMockBeanTests.java b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoMockBeanTests.java index a6a905e637..524bc9fe0a 100644 --- a/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoMockBeanTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoMockBeanTests.java @@ -29,27 +29,71 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException; * Tests for {@link MockitoBean}. * * @author Stephane Nicoll + * @author Sam Brannen */ class MockitoMockBeanTests { @Test - void contextCustomizerCannotBeCreatedWithTooManyCandidates() { + void cannotOverrideBeanByNameWithNoSuchBeanName() { + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBean("anotherBean", String.class, () -> "example"); + BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByNameLookup.class, context); + assertThatIllegalStateException() + .isThrownBy(context::refresh) + .withMessage(""" + Unable to override bean: there is no bean definition \ + to replace with name [beanToOverride] and type [java.lang.String]."""); + } + + @Test + void cannotOverrideBeanByNameWithBeanOfWrongType() { + GenericApplicationContext context = new GenericApplicationContext(); + context.registerBean("beanToOverride", Integer.class, () -> 42); + BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByNameLookup.class, context); + assertThatIllegalStateException() + .isThrownBy(context::refresh) + .withMessage(""" + Unable to override bean: there is no bean definition \ + to replace with name [beanToOverride] and type [java.lang.String]."""); + } + + @Test + void cannotOverrideBeanByTypeWithNoSuchBeanType() { + GenericApplicationContext context = new GenericApplicationContext(); + BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByTypeLookup.class, context); + assertThatIllegalStateException() + .isThrownBy(context::refresh) + .withMessage(""" + Unable to override bean: no bean definitions of \ + type %s (as required by annotated field '%s.example')""".formatted( + String.class.getName(), FailureByTypeLookup.class.getSimpleName())); + } + + @Test + void cannotOverrideBeanByTypeWithTooManyBeansOfThatType() { GenericApplicationContext context = new GenericApplicationContext(); context.registerBean("bean1", String.class, () -> "example1"); context.registerBean("bean2", String.class, () -> "example2"); - BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(ByTypeSingleLookup.class, context); + BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(FailureByTypeLookup.class, context); assertThatIllegalStateException() .isThrownBy(context::refresh) .withMessage(""" Unable to select a bean definition to override: found 2 bean definitions \ - of type %s (as required by annotated field '%s.example'): %s""", - String.class.getName(), ByTypeSingleLookup.class.getSimpleName(), List.of("bean1", "bean2")); + of type %s (as required by annotated field '%s.example'): %s""".formatted( + String.class.getName(), FailureByTypeLookup.class.getSimpleName(), List.of("bean1", "bean2"))); } - static class ByTypeSingleLookup { + static class FailureByTypeLookup { - @MockitoBean + @MockitoBean(enforceOverride = true) + String example; + + } + + static class FailureByNameLookup { + + @MockitoBean(name = "beanToOverride", enforceOverride = true) String example; }