From 3911ca7c8e0399d6408b959d982e0bbc5677d635 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 28 Jan 2025 19:26:48 -0800 Subject: [PATCH] Polish @ConditionalOnBean and @ConditionalOnMissingBean tests Combine generic and regular tests and apply some polish. See gh-29500 --- ...nditionalOnBeanGenericReturnTypeTests.java | 209 -------- .../condition/ConditionalOnBeanTests.java | 291 ++++++++--- ...alOnMissingBeanGenericReturnTypeTests.java | 204 -------- .../ConditionalOnMissingBeanTests.java | 475 ++++++++++++------ 4 files changed, 546 insertions(+), 633 deletions(-) delete mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanGenericReturnTypeTests.java delete mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanGenericReturnTypeTests.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanGenericReturnTypeTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanGenericReturnTypeTests.java deleted file mode 100644 index ba3c649cac..0000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanGenericReturnTypeTests.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright 2012-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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.condition; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.function.Consumer; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.util.StringUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link ConditionalOnBean @ConditionalOnBean} with generic bean return type. - * - * @author Uladzislau Seuruk - */ -class ConditionalOnBeanGenericReturnTypeTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); - - @Test - void genericWhenTypeArgumentMatches() { - this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, GenericWithStringTypeArgumentsConfig.class, - GenericWithIntegerTypeArgumentsConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("customExampleBean", "genericStringTypeArgumentsExampleBean"))); - } - - @Test - void genericWhenTypeArgumentWithValueMatches() { - this.contextRunner - .withUserConfiguration(GenericWithStringConfig.class, TypeArgumentsConditionWithValueConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("genericStringExampleBean", "genericStringWithValueExampleBean"))); - } - - @Test - void genericWithValueWhenSubclassTypeArgumentMatches() { - this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, TypeArgumentsConditionWithValueConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("customExampleBean", "genericStringWithValueExampleBean"))); - } - - @Test - void parameterizedContainerGenericWhenTypeArgumentNotMatches() { - this.contextRunner - .withUserConfiguration(GenericWithIntegerConfig.class, - TypeArgumentsConditionWithParameterizedContainerConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean"))); - } - - @Test - void parameterizedContainerGenericWhenTypeArgumentMatches() { - this.contextRunner - .withUserConfiguration(GenericWithStringConfig.class, - TypeArgumentsConditionWithParameterizedContainerConfig.class) - .run((context) -> assertThat(context).satisfies( - exampleBeanRequirement("genericStringExampleBean", "parameterizedContainerGenericExampleBean"))); - } - - @Test - void parameterizedContainerGenericWhenSubclassTypeArgumentMatches() { - this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, - TypeArgumentsConditionWithParameterizedContainerConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("customExampleBean", "parameterizedContainerGenericExampleBean"))); - } - - private Consumer exampleBeanRequirement(String... names) { - return (context) -> { - String[] beans = context.getBeanNamesForType(GenericExampleBean.class); - String[] containers = context.getBeanNamesForType(TestParameterizedContainer.class); - assertThat(StringUtils.concatenateStringArrays(beans, containers)).containsOnly(names); - }; - } - - @Configuration(proxyBeanMethods = false) - static class ParameterizedWithCustomConfig { - - @Bean - CustomExampleBean customExampleBean() { - return new CustomExampleBean(); - } - - } - - @Configuration(proxyBeanMethods = false) - static class GenericWithStringConfig { - - @Bean - GenericExampleBean genericStringExampleBean() { - return new GenericExampleBean<>("genericStringExampleBean"); - } - - } - - @Configuration(proxyBeanMethods = false) - static class GenericWithStringTypeArgumentsConfig { - - @Bean - @ConditionalOnBean - GenericExampleBean genericStringTypeArgumentsExampleBean() { - return new GenericExampleBean<>("genericStringTypeArgumentsExampleBean"); - } - - } - - @Configuration(proxyBeanMethods = false) - static class GenericWithIntegerConfig { - - @Bean - GenericExampleBean genericIntegerExampleBean() { - return new GenericExampleBean<>(1_000); - } - - } - - @Configuration(proxyBeanMethods = false) - static class GenericWithIntegerTypeArgumentsConfig { - - @Bean - @ConditionalOnBean - GenericExampleBean genericIntegerTypeArgumentsExampleBean() { - return new GenericExampleBean<>(1_000); - } - - } - - @Configuration(proxyBeanMethods = false) - static class TypeArgumentsConditionWithValueConfig { - - @Bean - @ConditionalOnBean(GenericExampleBean.class) - GenericExampleBean genericStringWithValueExampleBean() { - return new GenericExampleBean<>("genericStringWithValueExampleBean"); - } - - } - - @Configuration(proxyBeanMethods = false) - static class TypeArgumentsConditionWithParameterizedContainerConfig { - - @Bean - @ConditionalOnBean(parameterizedContainer = TestParameterizedContainer.class) - TestParameterizedContainer> parameterizedContainerGenericExampleBean() { - return new TestParameterizedContainer<>(); - } - - } - - @TestAnnotation - static class GenericExampleBean { - - private final T value; - - GenericExampleBean(T value) { - this.value = value; - } - - @Override - public String toString() { - return String.valueOf(this.value); - } - - } - - static class CustomExampleBean extends GenericExampleBean { - - CustomExampleBean() { - super("custom subclass"); - } - - } - - @Target(ElementType.TYPE) - @Retention(RetentionPolicy.RUNTIME) - @Documented - @interface TestAnnotation { - - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java index 01d852c0cd..53d9eba239 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-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. @@ -52,6 +52,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Dave Syer * @author Stephane Nicoll + * @author Uladzislau Seuruk */ class ConditionalOnBeanTests { @@ -116,8 +117,8 @@ class ConditionalOnBeanTests { @Test void withPropertyPlaceholderClassName() { this.contextRunner - .withUserConfiguration(PropertySourcesPlaceholderConfigurer.class, WithPropertyPlaceholderClassName.class, - OnBeanClassConfiguration.class) + .withUserConfiguration(PropertySourcesPlaceholderConfigurer.class, + WithPropertyPlaceholderClassNameConfiguration.class, OnBeanClassConfiguration.class) .withPropertyValues("mybeanclass=java.lang.String") .run((context) -> assertThat(context).hasNotFailed()); } @@ -165,7 +166,8 @@ class ConditionalOnBeanTests { @Test void conditionEvaluationConsidersChangeInTypeWhenBeanIsOverridden() { this.contextRunner.withAllowBeanDefinitionOverriding(true) - .withUserConfiguration(OriginalDefinition.class, OverridingDefinition.class, ConsumingConfiguration.class) + .withUserConfiguration(OriginalDefinitionConfiguration.class, OverridingDefinitionConfiguration.class, + ConsumingConfiguration.class) .run((context) -> { assertThat(context).hasBean("testBean"); assertThat(context).hasSingleBean(Integer.class); @@ -176,69 +178,73 @@ class ConditionalOnBeanTests { @Test void parameterizedContainerWhenValueIsOfMissingBeanDoesNotMatch() { this.contextRunner - .withUserConfiguration(ParameterizedWithoutCustomConfig.class, ParameterizedConditionWithValueConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("otherExampleBean"))); + .withUserConfiguration(ParameterizedWithoutCustomConfiguration.class, + ParameterizedConditionWithValueConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(ExampleBean.class, "otherExampleBean"))); } @Test void parameterizedContainerWhenValueIsOfExistingBeanMatches() { this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, ParameterizedConditionWithValueConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("customExampleBean", "conditionalCustomExampleBean"))); + .withUserConfiguration(ParameterizedWithCustomConfiguration.class, + ParameterizedConditionWithValueConfiguration.class) + .run((context) -> assertThat(context).satisfies( + beansAndContainersNamed(ExampleBean.class, "customExampleBean", "conditionalCustomExampleBean"))); } @Test void parameterizedContainerWhenValueIsOfMissingBeanRegistrationDoesNotMatch() { this.contextRunner - .withUserConfiguration(ParameterizedWithoutCustomContainerConfig.class, - ParameterizedConditionWithValueConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("otherExampleBean"))); + .withUserConfiguration(ParameterizedWithoutCustomContainerConfiguration.class, + ParameterizedConditionWithValueConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(ExampleBean.class, "otherExampleBean"))); } @Test void parameterizedContainerWhenValueIsOfExistingBeanRegistrationMatches() { this.contextRunner - .withUserConfiguration(ParameterizedWithCustomContainerConfig.class, - ParameterizedConditionWithValueConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("customExampleBean", "conditionalCustomExampleBean"))); + .withUserConfiguration(ParameterizedWithCustomContainerConfiguration.class, + ParameterizedConditionWithValueConfiguration.class) + .run((context) -> assertThat(context).satisfies( + beansAndContainersNamed(ExampleBean.class, "customExampleBean", "conditionalCustomExampleBean"))); } @Test void parameterizedContainerWhenReturnTypeIsOfExistingBeanMatches() { this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, - ParameterizedConditionWithReturnTypeConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("customExampleBean", "conditionalCustomExampleBean"))); + .withUserConfiguration(ParameterizedWithCustomConfiguration.class, + ParameterizedConditionWithReturnTypeConfiguration.class) + .run((context) -> assertThat(context).satisfies( + beansAndContainersNamed(ExampleBean.class, "customExampleBean", "conditionalCustomExampleBean"))); } @Test void parameterizedContainerWhenReturnTypeIsOfExistingBeanRegistrationMatches() { this.contextRunner - .withUserConfiguration(ParameterizedWithCustomContainerConfig.class, - ParameterizedConditionWithReturnTypeConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("customExampleBean", "conditionalCustomExampleBean"))); + .withUserConfiguration(ParameterizedWithCustomContainerConfiguration.class, + ParameterizedConditionWithReturnTypeConfiguration.class) + .run((context) -> assertThat(context).satisfies( + beansAndContainersNamed(ExampleBean.class, "customExampleBean", "conditionalCustomExampleBean"))); } @Test void parameterizedContainerWhenReturnRegistrationTypeIsOfExistingBeanMatches() { this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, - ParameterizedConditionWithReturnRegistrationTypeConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("customExampleBean", "conditionalCustomExampleBean"))); + .withUserConfiguration(ParameterizedWithCustomConfiguration.class, + ParameterizedConditionWithReturnRegistrationTypeConfiguration.class) + .run((context) -> assertThat(context).satisfies( + beansAndContainersNamed(ExampleBean.class, "customExampleBean", "conditionalCustomExampleBean"))); } @Test void parameterizedContainerWhenReturnRegistrationTypeIsOfExistingBeanRegistrationMatches() { this.contextRunner - .withUserConfiguration(ParameterizedWithCustomContainerConfig.class, - ParameterizedConditionWithReturnRegistrationTypeConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("customExampleBean", "conditionalCustomExampleBean"))); + .withUserConfiguration(ParameterizedWithCustomContainerConfiguration.class, + ParameterizedConditionWithReturnRegistrationTypeConfiguration.class) + .run((context) -> assertThat(context).satisfies( + beansAndContainersNamed(ExampleBean.class, "customExampleBean", "conditionalCustomExampleBean"))); } @Test @@ -257,7 +263,7 @@ class ConditionalOnBeanTests { @Test void conditionalOnAnnotatedBeanIgnoresNotAutowireCandidateBean() { this.contextRunner - .withUserConfiguration(AnnotatedNotAutowireCandidateConfig.class, OnAnnotationConfiguration.class) + .withUserConfiguration(AnnotatedNotAutowireCandidateConfiguration.class, OnAnnotationConfiguration.class) .run((context) -> assertThat(context).doesNotHaveBean("bar")); } @@ -284,13 +290,66 @@ class ConditionalOnBeanTests { @Test void conditionalOnAnnotatedBeanIgnoresNotDefaultCandidateBean() { this.contextRunner - .withUserConfiguration(AnnotatedNotDefaultCandidateConfig.class, OnAnnotationConfiguration.class) + .withUserConfiguration(AnnotatedNotDefaultCandidateConfiguration.class, OnAnnotationConfiguration.class) .run((context) -> assertThat(context).doesNotHaveBean("bar")); } - private Consumer exampleBeanRequirement(String... names) { + @Test + void genericWhenTypeArgumentMatches() { + this.contextRunner.withUserConfiguration(ParameterizedWithCustomGenericConfiguration.class, + GenericWithStringTypeArgumentsConfiguration.class, GenericWithIntegerTypeArgumentsConfiguration.class) + .run((context) -> assertThat(context).satisfies(beansAndContainersNamed(GenericExampleBean.class, + "customGenericExampleBean", "genericStringTypeArgumentsExampleBean"))); + } + + @Test + void genericWhenTypeArgumentWithValueMatches() { + this.contextRunner + .withUserConfiguration(GenericWithStringConfiguration.class, + TypeArgumentsConditionWithValueConfiguration.class) + .run((context) -> assertThat(context).satisfies(beansAndContainersNamed(GenericExampleBean.class, + "genericStringExampleBean", "genericStringWithValueExampleBean"))); + } + + @Test + void genericWithValueWhenSubclassTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomGenericConfiguration.class, + TypeArgumentsConditionWithValueConfiguration.class) + .run((context) -> assertThat(context).satisfies(beansAndContainersNamed(GenericExampleBean.class, + "customGenericExampleBean", "genericStringWithValueExampleBean"))); + } + + @Test + void parameterizedContainerGenericWhenTypeArgumentNotMatches() { + this.contextRunner + .withUserConfiguration(GenericWithIntegerConfiguration.class, + TypeArgumentsConditionWithParameterizedContainerConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(GenericExampleBean.class, "genericIntegerExampleBean"))); + } + + @Test + void parameterizedContainerGenericWhenTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(GenericWithStringConfiguration.class, + TypeArgumentsConditionWithParameterizedContainerConfiguration.class) + .run((context) -> assertThat(context).satisfies(beansAndContainersNamed(GenericExampleBean.class, + "genericStringExampleBean", "parameterizedContainerGenericExampleBean"))); + } + + @Test + void parameterizedContainerGenericWhenSubclassTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomGenericConfiguration.class, + TypeArgumentsConditionWithParameterizedContainerConfiguration.class) + .run((context) -> assertThat(context).satisfies(beansAndContainersNamed(GenericExampleBean.class, + "customGenericExampleBean", "parameterizedContainerGenericExampleBean"))); + } + + private Consumer beansAndContainersNamed(Class type, String... names) { return (context) -> { - String[] beans = context.getBeanNamesForType(ExampleBean.class); + String[] beans = context.getBeanNamesForType(type); String[] containers = context.getBeanNamesForType(TestParameterizedContainer.class); assertThat(StringUtils.concatenateStringArrays(beans, containers)).containsOnly(names); }; @@ -429,7 +488,7 @@ class ConditionalOnBeanTests { @Configuration(proxyBeanMethods = false) @Import(WithPropertyPlaceholderClassNameRegistrar.class) - static class WithPropertyPlaceholderClassName { + static class WithPropertyPlaceholderClassNameConfiguration { } @@ -495,27 +554,8 @@ class ConditionalOnBeanTests { } - static class ExampleFactoryBean implements FactoryBean { - - @Override - public ExampleBean getObject() { - return new ExampleBean("fromFactory"); - } - - @Override - public Class getObjectType() { - return ExampleBean.class; - } - - @Override - public boolean isSingleton() { - return false; - } - - } - @Configuration(proxyBeanMethods = false) - static class OriginalDefinition { + static class OriginalDefinitionConfiguration { @Bean String testBean() { @@ -526,7 +566,7 @@ class ConditionalOnBeanTests { @Configuration(proxyBeanMethods = false) @ConditionalOnBean(String.class) - static class OverridingDefinition { + static class OverridingDefinitionConfiguration { @Bean Integer testBean() { @@ -545,7 +585,7 @@ class ConditionalOnBeanTests { } @Configuration(proxyBeanMethods = false) - static class ParameterizedWithCustomConfig { + static class ParameterizedWithCustomConfiguration { @Bean CustomExampleBean customExampleBean() { @@ -555,7 +595,7 @@ class ConditionalOnBeanTests { } @Configuration(proxyBeanMethods = false) - static class ParameterizedWithoutCustomConfig { + static class ParameterizedWithoutCustomConfiguration { @Bean OtherExampleBean otherExampleBean() { @@ -565,7 +605,7 @@ class ConditionalOnBeanTests { } @Configuration(proxyBeanMethods = false) - static class ParameterizedWithoutCustomContainerConfig { + static class ParameterizedWithoutCustomContainerConfiguration { @Bean TestParameterizedContainer otherExampleBean() { @@ -575,7 +615,7 @@ class ConditionalOnBeanTests { } @Configuration(proxyBeanMethods = false) - static class ParameterizedWithCustomContainerConfig { + static class ParameterizedWithCustomContainerConfiguration { @Bean TestParameterizedContainer customExampleBean() { @@ -585,7 +625,7 @@ class ConditionalOnBeanTests { } @Configuration(proxyBeanMethods = false) - static class ParameterizedConditionWithValueConfig { + static class ParameterizedConditionWithValueConfiguration { @Bean @ConditionalOnBean(value = CustomExampleBean.class, parameterizedContainer = TestParameterizedContainer.class) @@ -596,7 +636,7 @@ class ConditionalOnBeanTests { } @Configuration(proxyBeanMethods = false) - static class ParameterizedConditionWithReturnTypeConfig { + static class ParameterizedConditionWithReturnTypeConfiguration { @Bean @ConditionalOnBean(parameterizedContainer = TestParameterizedContainer.class) @@ -607,7 +647,7 @@ class ConditionalOnBeanTests { } @Configuration(proxyBeanMethods = false) - static class ParameterizedConditionWithReturnRegistrationTypeConfig { + static class ParameterizedConditionWithReturnRegistrationTypeConfiguration { @Bean @ConditionalOnBean(parameterizedContainer = TestParameterizedContainer.class) @@ -618,7 +658,7 @@ class ConditionalOnBeanTests { } @Configuration(proxyBeanMethods = false) - static class AnnotatedNotAutowireCandidateConfig { + static class AnnotatedNotAutowireCandidateConfiguration { @Bean(autowireCandidate = false) ExampleBean exampleBean() { @@ -628,7 +668,7 @@ class ConditionalOnBeanTests { } @Configuration(proxyBeanMethods = false) - static class AnnotatedNotDefaultCandidateConfig { + static class AnnotatedNotDefaultCandidateConfiguration { @Bean(defaultCandidate = false) ExampleBean exampleBean() { @@ -637,6 +677,99 @@ class ConditionalOnBeanTests { } + @Configuration(proxyBeanMethods = false) + static class ParameterizedWithCustomGenericConfiguration { + + @Bean + CustomGenericExampleBean customGenericExampleBean() { + return new CustomGenericExampleBean(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithStringConfiguration { + + @Bean + GenericExampleBean genericStringExampleBean() { + return new GenericExampleBean<>("genericStringExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithStringTypeArgumentsConfiguration { + + @Bean + @ConditionalOnBean + GenericExampleBean genericStringTypeArgumentsExampleBean() { + return new GenericExampleBean<>("genericStringTypeArgumentsExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithIntegerConfiguration { + + @Bean + GenericExampleBean genericIntegerExampleBean() { + return new GenericExampleBean<>(1_000); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithIntegerTypeArgumentsConfiguration { + + @Bean + @ConditionalOnBean + GenericExampleBean genericIntegerTypeArgumentsExampleBean() { + return new GenericExampleBean<>(1_000); + } + + } + + @Configuration(proxyBeanMethods = false) + static class TypeArgumentsConditionWithValueConfiguration { + + @Bean + @ConditionalOnBean(GenericExampleBean.class) + GenericExampleBean genericStringWithValueExampleBean() { + return new GenericExampleBean<>("genericStringWithValueExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class TypeArgumentsConditionWithParameterizedContainerConfiguration { + + @Bean + @ConditionalOnBean(parameterizedContainer = TestParameterizedContainer.class) + TestParameterizedContainer> parameterizedContainerGenericExampleBean() { + return new TestParameterizedContainer<>(); + } + + } + + static class ExampleFactoryBean implements FactoryBean { + + @Override + public ExampleBean getObject() { + return new ExampleBean("fromFactory"); + } + + @Override + public Class getObjectType() { + return ExampleBean.class; + } + + @Override + public boolean isSingleton() { + return false; + } + + } + @TestAnnotation static class ExampleBean { @@ -669,6 +802,30 @@ class ConditionalOnBeanTests { } + @TestAnnotation + static class GenericExampleBean { + + private final T value; + + GenericExampleBean(T value) { + this.value = value; + } + + @Override + public String toString() { + return String.valueOf(this.value); + } + + } + + static class CustomGenericExampleBean extends GenericExampleBean { + + CustomGenericExampleBean() { + super("custom subclass"); + } + + } + @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanGenericReturnTypeTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanGenericReturnTypeTests.java deleted file mode 100644 index 3f60bd24e3..0000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanGenericReturnTypeTests.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2012-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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.condition; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.function.Consumer; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.util.StringUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link ConditionalOnMissingBean @ConditionalOnMissingBean} with generic bean - * return type. - * - * @author Uladzislau Seuruk - */ -@SuppressWarnings("resource") -class ConditionalOnMissingBeanGenericReturnTypeTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); - - @Test - void genericWhenTypeArgumentNotMatches() { - this.contextRunner - .withUserConfiguration(GenericWithStringTypeArgumentsConfig.class, - GenericWithIntegerTypeArgumentsConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("genericStringExampleBean", "genericIntegerExampleBean"))); - } - - @Test - void genericWhenSubclassTypeArgumentMatches() { - this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, GenericWithStringTypeArgumentsConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); - } - - @Test - void genericWhenSubclassTypeArgumentNotMatches() { - this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, GenericWithIntegerTypeArgumentsConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("customExampleBean", "genericIntegerExampleBean"))); - } - - @Test - void genericWhenTypeArgumentWithValueMatches() { - this.contextRunner - .withUserConfiguration(GenericWithStringTypeArgumentsConfig.class, - TypeArgumentsConditionWithValueConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean"))); - } - - @Test - void genericWithValueWhenSubclassTypeArgumentMatches() { - this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, TypeArgumentsConditionWithValueConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); - } - - @Test - void parameterizedContainerGenericWhenTypeArgumentNotMatches() { - this.contextRunner - .withUserConfiguration(GenericWithIntegerTypeArgumentsConfig.class, - TypeArgumentsConditionWithParameterizedContainerConfig.class) - .run((context) -> assertThat(context).satisfies( - exampleBeanRequirement("genericIntegerExampleBean", "parameterizedContainerGenericExampleBean"))); - } - - @Test - void parameterizedContainerGenericWhenTypeArgumentMatches() { - this.contextRunner - .withUserConfiguration(GenericWithStringTypeArgumentsConfig.class, - TypeArgumentsConditionWithParameterizedContainerConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean"))); - } - - @Test - void parameterizedContainerGenericWhenSubclassTypeArgumentMatches() { - this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, - TypeArgumentsConditionWithParameterizedContainerConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); - } - - private Consumer exampleBeanRequirement(String... names) { - return (context) -> { - String[] beans = context.getBeanNamesForType(GenericExampleBean.class); - String[] containers = context.getBeanNamesForType(TestParameterizedContainer.class); - assertThat(StringUtils.concatenateStringArrays(beans, containers)).containsOnly(names); - }; - } - - @Configuration(proxyBeanMethods = false) - static class ParameterizedWithCustomConfig { - - @Bean - CustomExampleBean customExampleBean() { - return new CustomExampleBean(); - } - - } - - @Configuration(proxyBeanMethods = false) - static class GenericWithStringTypeArgumentsConfig { - - @Bean - @ConditionalOnMissingBean - GenericExampleBean genericStringExampleBean() { - return new GenericExampleBean<>("genericStringExampleBean"); - } - - } - - @Configuration(proxyBeanMethods = false) - static class GenericWithIntegerTypeArgumentsConfig { - - @Bean - @ConditionalOnMissingBean - GenericExampleBean genericIntegerExampleBean() { - return new GenericExampleBean<>(1_000); - } - - } - - @Configuration(proxyBeanMethods = false) - static class TypeArgumentsConditionWithValueConfig { - - @Bean - @ConditionalOnMissingBean(GenericExampleBean.class) - GenericExampleBean genericStringWithValueExampleBean() { - return new GenericExampleBean<>("genericStringWithValueExampleBean"); - } - - } - - @Configuration(proxyBeanMethods = false) - static class TypeArgumentsConditionWithParameterizedContainerConfig { - - @Bean - @ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class) - TestParameterizedContainer> parameterizedContainerGenericExampleBean() { - return new TestParameterizedContainer<>(); - } - - } - - @TestAnnotation - static class GenericExampleBean { - - private final T value; - - GenericExampleBean(T value) { - this.value = value; - } - - @Override - public String toString() { - return String.valueOf(this.value); - } - - } - - static class CustomExampleBean extends GenericExampleBean { - - CustomExampleBean() { - super("custom subclass"); - } - - } - - @Target(ElementType.TYPE) - @Retention(RetentionPolicy.RUNTIME) - @Documented - @interface TestAnnotation { - - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java index b908dc67a0..7b5e44da3e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-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. @@ -61,6 +61,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Phillip Webb * @author Jakub Kubrynski * @author Andy Wilkinson + * @author Uladzislau Seuruk */ @SuppressWarnings("resource") class ConditionalOnMissingBeanTests { @@ -100,7 +101,7 @@ class ConditionalOnMissingBeanTests { void hierarchyConsidered() { this.contextRunner.withUserConfiguration(FooConfiguration.class) .run((parent) -> new ApplicationContextRunner().withParent(parent) - .withUserConfiguration(HierarchyConsidered.class) + .withUserConfiguration(HierarchyConsideredConfiguration.class) .run((context) -> assertThat(context.containsLocalBean("bar")).isFalse())); } @@ -108,13 +109,13 @@ class ConditionalOnMissingBeanTests { void hierarchyNotConsidered() { this.contextRunner.withUserConfiguration(FooConfiguration.class) .run((parent) -> new ApplicationContextRunner().withParent(parent) - .withUserConfiguration(HierarchyNotConsidered.class) + .withUserConfiguration(HierarchyNotConsideredConfiguration.class) .run((context) -> assertThat(context.containsLocalBean("bar")).isTrue())); } @Test void impliedOnBeanMethod() { - this.contextRunner.withUserConfiguration(ExampleBeanConfiguration.class, ImpliedOnBeanMethod.class) + this.contextRunner.withUserConfiguration(ExampleBeanConfiguration.class, ImpliedOnBeanMethodConfiguration.class) .run((context) -> assertThat(context).hasSingleBean(ExampleBean.class)); } @@ -173,7 +174,8 @@ class ConditionalOnMissingBeanTests { @Test void testOnMissingBeanConditionWithFactoryBean() { this.contextRunner - .withUserConfiguration(FactoryBeanConfiguration.class, ConditionalOnMissingBeanProducedByFactoryBean.class, + .withUserConfiguration(FactoryBeanConfiguration.class, + ConditionalOnMissingBeanProducedByFactoryBeanConfiguration.class, PropertyPlaceholderAutoConfiguration.class) .run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory")); } @@ -182,7 +184,8 @@ class ConditionalOnMissingBeanTests { void testOnMissingBeanConditionWithComponentScannedFactoryBean() { this.contextRunner .withUserConfiguration(ComponentScannedFactoryBeanBeanMethodConfiguration.class, - ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) + ConditionalOnMissingBeanProducedByFactoryBeanConfiguration.class, + PropertyPlaceholderAutoConfiguration.class) .run((context) -> assertThat(context.getBean(ScanBean.class)).hasToString("fromFactory")); } @@ -190,7 +193,8 @@ class ConditionalOnMissingBeanTests { void testOnMissingBeanConditionWithComponentScannedFactoryBeanWithBeanMethodArguments() { this.contextRunner .withUserConfiguration(ComponentScannedFactoryBeanBeanMethodWithArgumentsConfiguration.class, - ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) + ConditionalOnMissingBeanProducedByFactoryBeanConfiguration.class, + PropertyPlaceholderAutoConfiguration.class) .run((context) -> assertThat(context.getBean(ScanBean.class)).hasToString("fromFactory")); } @@ -198,7 +202,8 @@ class ConditionalOnMissingBeanTests { void testOnMissingBeanConditionWithFactoryBeanWithBeanMethodArguments() { this.contextRunner .withUserConfiguration(FactoryBeanWithBeanMethodArgumentsConfiguration.class, - ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) + ConditionalOnMissingBeanProducedByFactoryBeanConfiguration.class, + PropertyPlaceholderAutoConfiguration.class) .withPropertyValues("theValue=foo") .run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory")); } @@ -207,7 +212,8 @@ class ConditionalOnMissingBeanTests { void testOnMissingBeanConditionWithConcreteFactoryBean() { this.contextRunner .withUserConfiguration(ConcreteFactoryBeanConfiguration.class, - ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) + ConditionalOnMissingBeanProducedByFactoryBeanConfiguration.class, + PropertyPlaceholderAutoConfiguration.class) .run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory")); } @@ -216,7 +222,8 @@ class ConditionalOnMissingBeanTests { // We could not tell that the FactoryBean would ultimately create an ExampleBean this.contextRunner .withUserConfiguration(UnhelpfulFactoryBeanConfiguration.class, - ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) + ConditionalOnMissingBeanProducedByFactoryBeanConfiguration.class, + PropertyPlaceholderAutoConfiguration.class) .run((context) -> assertThat(context).getBeans(ExampleBean.class).hasSize(2)); } @@ -224,7 +231,8 @@ class ConditionalOnMissingBeanTests { void testOnMissingBeanConditionWithRegisteredFactoryBean() { this.contextRunner .withUserConfiguration(RegisteredFactoryBeanConfiguration.class, - ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) + ConditionalOnMissingBeanProducedByFactoryBeanConfiguration.class, + PropertyPlaceholderAutoConfiguration.class) .run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory")); } @@ -232,7 +240,8 @@ class ConditionalOnMissingBeanTests { void testOnMissingBeanConditionWithNonspecificFactoryBeanWithClassAttribute() { this.contextRunner .withUserConfiguration(NonspecificFactoryBeanClassAttributeConfiguration.class, - ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) + ConditionalOnMissingBeanProducedByFactoryBeanConfiguration.class, + PropertyPlaceholderAutoConfiguration.class) .run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory")); } @@ -240,7 +249,8 @@ class ConditionalOnMissingBeanTests { void testOnMissingBeanConditionWithNonspecificFactoryBeanWithStringAttribute() { this.contextRunner .withUserConfiguration(NonspecificFactoryBeanStringAttributeConfiguration.class, - ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) + ConditionalOnMissingBeanProducedByFactoryBeanConfiguration.class, + PropertyPlaceholderAutoConfiguration.class) .run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory")); } @@ -248,15 +258,16 @@ class ConditionalOnMissingBeanTests { void testOnMissingBeanConditionWithFactoryBeanInXml() { this.contextRunner .withUserConfiguration(FactoryBeanXmlConfiguration.class, - ConditionalOnMissingBeanProducedByFactoryBean.class, PropertyPlaceholderAutoConfiguration.class) + ConditionalOnMissingBeanProducedByFactoryBeanConfiguration.class, + PropertyPlaceholderAutoConfiguration.class) .run((context) -> assertThat(context.getBean(ExampleBean.class)).hasToString("fromFactory")); } @Test void testOnMissingBeanConditionWithIgnoredSubclass() { this.contextRunner - .withUserConfiguration(CustomExampleBeanConfiguration.class, ConditionalOnIgnoredSubclass.class, - PropertyPlaceholderAutoConfiguration.class) + .withUserConfiguration(CustomExampleBeanConfiguration.class, + ConditionalOnIgnoredSubclassConfiguration.class, PropertyPlaceholderAutoConfiguration.class) .run((context) -> { assertThat(context).getBeans(ExampleBean.class).hasSize(2); assertThat(context).getBeans(CustomExampleBean.class).hasSize(1); @@ -266,8 +277,8 @@ class ConditionalOnMissingBeanTests { @Test void testOnMissingBeanConditionWithIgnoredSubclassByName() { this.contextRunner - .withUserConfiguration(CustomExampleBeanConfiguration.class, ConditionalOnIgnoredSubclassByName.class, - PropertyPlaceholderAutoConfiguration.class) + .withUserConfiguration(CustomExampleBeanConfiguration.class, + ConditionalOnIgnoredSubclassByNameConfiguration.class, PropertyPlaceholderAutoConfiguration.class) .run((context) -> { assertThat(context).getBeans(ExampleBean.class).hasSize(2); assertThat(context).getBeans(CustomExampleBean.class).hasSize(1); @@ -304,89 +315,97 @@ class ConditionalOnMissingBeanTests { @Test void parameterizedContainerWhenValueIsOfMissingBeanMatches() { this.contextRunner - .withUserConfiguration(ParameterizedWithoutCustomConfig.class, ParameterizedConditionWithValueConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("otherExampleBean", "conditionalCustomExampleBean"))); + .withUserConfiguration(ParameterizedWithoutCustomConfiguration.class, + ParameterizedConditionWithValueConfiguration.class) + .run((context) -> assertThat(context).satisfies( + beansAndContainersNamed(ExampleBean.class, "otherExampleBean", "conditionalCustomExampleBean"))); } @Test void parameterizedContainerWhenValueIsOfExistingBeanDoesNotMatch() { this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, ParameterizedConditionWithValueConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); + .withUserConfiguration(ParameterizedWithCustomConfiguration.class, + ParameterizedConditionWithValueConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(ExampleBean.class, "customExampleBean"))); } @Test void parameterizedContainerWhenValueIsOfMissingBeanRegistrationMatches() { this.contextRunner - .withUserConfiguration(ParameterizedWithoutCustomContainerConfig.class, - ParameterizedConditionWithValueConfig.class) - .run((context) -> assertThat(context) - .satisfies(exampleBeanRequirement("otherExampleBean", "conditionalCustomExampleBean"))); + .withUserConfiguration(ParameterizedWithoutCustomContainerConfiguration.class, + ParameterizedConditionWithValueConfiguration.class) + .run((context) -> assertThat(context).satisfies( + beansAndContainersNamed(ExampleBean.class, "otherExampleBean", "conditionalCustomExampleBean"))); } @Test void parameterizedContainerWhenValueIsOfExistingBeanRegistrationDoesNotMatch() { this.contextRunner - .withUserConfiguration(ParameterizedWithCustomContainerConfig.class, - ParameterizedConditionWithValueConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); + .withUserConfiguration(ParameterizedWithCustomContainerConfiguration.class, + ParameterizedConditionWithValueConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(ExampleBean.class, "customExampleBean"))); } @Test void parameterizedContainerWhenReturnTypeIsOfExistingBeanDoesNotMatch() { this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, - ParameterizedConditionWithReturnTypeConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); + .withUserConfiguration(ParameterizedWithCustomConfiguration.class, + ParameterizedConditionWithReturnTypeConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(ExampleBean.class, "customExampleBean"))); } @Test void parameterizedContainerWhenReturnTypeIsOfExistingBeanRegistrationDoesNotMatch() { this.contextRunner - .withUserConfiguration(ParameterizedWithCustomContainerConfig.class, - ParameterizedConditionWithReturnTypeConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); + .withUserConfiguration(ParameterizedWithCustomContainerConfiguration.class, + ParameterizedConditionWithReturnTypeConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(ExampleBean.class, "customExampleBean"))); } @Test void parameterizedContainerWhenReturnRegistrationTypeIsOfExistingBeanDoesNotMatch() { this.contextRunner - .withUserConfiguration(ParameterizedWithCustomConfig.class, - ParameterizedConditionWithReturnRegistrationTypeConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); + .withUserConfiguration(ParameterizedWithCustomConfiguration.class, + ParameterizedConditionWithReturnRegistrationTypeConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(ExampleBean.class, "customExampleBean"))); } @Test void parameterizedContainerWhenReturnRegistrationTypeIsOfExistingBeanRegistrationDoesNotMatch() { this.contextRunner - .withUserConfiguration(ParameterizedWithCustomContainerConfig.class, - ParameterizedConditionWithReturnRegistrationTypeConfig.class) - .run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean"))); + .withUserConfiguration(ParameterizedWithCustomContainerConfiguration.class, + ParameterizedConditionWithReturnRegistrationTypeConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(ExampleBean.class, "customExampleBean"))); } @Test void typeBasedMatchingIgnoresBeanThatIsNotAutowireCandidate() { - this.contextRunner.withUserConfiguration(NotAutowireCandidateConfig.class, OnBeanTypeConfiguration.class) + this.contextRunner.withUserConfiguration(NotAutowireCandidateConfiguration.class, OnBeanTypeConfiguration.class) .run((context) -> assertThat(context).hasBean("bar")); } @Test void nameBasedMatchingConsidersBeanThatIsNotAutowireCandidate() { - this.contextRunner.withUserConfiguration(NotAutowireCandidateConfig.class, OnBeanNameConfiguration.class) + this.contextRunner.withUserConfiguration(NotAutowireCandidateConfiguration.class, OnBeanNameConfiguration.class) .run((context) -> assertThat(context).doesNotHaveBean("bar")); } @Test void annotationBasedMatchingIgnoresBeanThatIsNotAutowireCandidateBean() { this.contextRunner - .withUserConfiguration(AnnotatedNotAutowireCandidateConfig.class, OnAnnotationConfiguration.class) + .withUserConfiguration(AnnotatedNotAutowireCandidateConfiguration.class, OnAnnotationConfiguration.class) .run((context) -> assertThat(context).hasBean("bar")); } @Test void typeBasedMatchingIgnoresBeanThatIsNotDefaultCandidate() { - this.contextRunner.withUserConfiguration(NotDefaultCandidateConfig.class, OnBeanTypeConfiguration.class) + this.contextRunner.withUserConfiguration(NotDefaultCandidateConfiguration.class, OnBeanTypeConfiguration.class) .run((context) -> assertThat(context).hasBean("bar")); } @@ -394,27 +413,99 @@ class ConditionalOnMissingBeanTests { void typeBasedMatchingIgnoresFactoryBeanThatIsNotDefaultCandidate() { this.contextRunner .withUserConfiguration(NotDefaultCandidateFactoryBeanConfiguration.class, - ConditionalOnMissingFactoryBean.class) + ConditionalOnMissingFactoryBeanConfiguration.class) .run((context) -> assertThat(context).hasBean("&exampleFactoryBean") .hasBean("&additionalExampleFactoryBean")); } @Test void nameBasedMatchingConsidersBeanThatIsNotDefaultCandidate() { - this.contextRunner.withUserConfiguration(NotDefaultCandidateConfig.class, OnBeanNameConfiguration.class) + this.contextRunner.withUserConfiguration(NotDefaultCandidateConfiguration.class, OnBeanNameConfiguration.class) .run((context) -> assertThat(context).doesNotHaveBean("bar")); } @Test void annotationBasedMatchingIgnoresBeanThatIsNotDefaultCandidateBean() { this.contextRunner - .withUserConfiguration(AnnotatedNotDefaultCandidateConfig.class, OnAnnotationConfiguration.class) + .withUserConfiguration(AnnotatedNotDefaultCandidateConfiguration.class, OnAnnotationConfiguration.class) .run((context) -> assertThat(context).hasBean("bar")); } - private Consumer exampleBeanRequirement(String... names) { + @Test + void genericWhenTypeArgumentNotMatches() { + this.contextRunner + .withUserConfiguration(GenericWithStringTypeArgumentsConfiguration.class, + GenericWithIntegerTypeArgumentsConfiguration.class) + .run((context) -> assertThat(context).satisfies(beansAndContainersNamed(GenericExampleBean.class, + "genericStringExampleBean", "genericIntegerExampleBean"))); + } + + @Test + void genericWhenSubclassTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomGenericConfiguration.class, + GenericWithStringTypeArgumentsConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(GenericExampleBean.class, "customGenericExampleBean"))); + } + + @Test + void genericWhenSubclassTypeArgumentNotMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomGenericConfiguration.class, + GenericWithIntegerTypeArgumentsConfiguration.class) + .run((context) -> assertThat(context).satisfies(beansAndContainersNamed(GenericExampleBean.class, + "customGenericExampleBean", "genericIntegerExampleBean"))); + } + + @Test + void genericWhenTypeArgumentWithValueMatches() { + this.contextRunner + .withUserConfiguration(GenericWithStringTypeArgumentsConfiguration.class, + TypeArgumentsConditionWithValueConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(GenericExampleBean.class, "genericStringExampleBean"))); + } + + @Test + void genericWithValueWhenSubclassTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomGenericConfiguration.class, + TypeArgumentsConditionWithValueConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(GenericExampleBean.class, "customGenericExampleBean"))); + } + + @Test + void parameterizedContainerGenericWhenTypeArgumentNotMatches() { + this.contextRunner + .withUserConfiguration(GenericWithIntegerTypeArgumentsConfiguration.class, + TypeArgumentsConditionWithParameterizedContainerConfiguration.class) + .run((context) -> assertThat(context).satisfies(beansAndContainersNamed(GenericExampleBean.class, + "genericIntegerExampleBean", "parameterizedContainerGenericExampleBean"))); + } + + @Test + void parameterizedContainerGenericWhenTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(GenericWithStringTypeArgumentsConfiguration.class, + TypeArgumentsConditionWithParameterizedContainerConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(GenericExampleBean.class, "genericStringExampleBean"))); + } + + @Test + void parameterizedContainerGenericWhenSubclassTypeArgumentMatches() { + this.contextRunner + .withUserConfiguration(ParameterizedWithCustomGenericConfiguration.class, + TypeArgumentsConditionWithParameterizedContainerConfiguration.class) + .run((context) -> assertThat(context) + .satisfies(beansAndContainersNamed(GenericExampleBean.class, "customGenericExampleBean"))); + } + + private Consumer beansAndContainersNamed(Class type, String... names) { return (context) -> { - String[] beans = context.getBeanNamesForType(ExampleBean.class); + String[] beans = context.getBeanNamesForType(type); String[] containers = context.getBeanNamesForType(TestParameterizedContainer.class); assertThat(StringUtils.concatenateStringArrays(beans, containers)).containsOnly(names); }; @@ -584,7 +675,7 @@ class ConditionalOnMissingBeanTests { } @Configuration(proxyBeanMethods = false) - static class ConditionalOnMissingBeanProducedByFactoryBean { + static class ConditionalOnMissingBeanProducedByFactoryBeanConfiguration { @Bean @ConditionalOnMissingBean @@ -595,7 +686,7 @@ class ConditionalOnMissingBeanTests { } @Configuration(proxyBeanMethods = false) - static class ConditionalOnMissingFactoryBean { + static class ConditionalOnMissingFactoryBeanConfiguration { @Bean @ConditionalOnMissingBean @@ -606,7 +697,7 @@ class ConditionalOnMissingBeanTests { } @Configuration(proxyBeanMethods = false) - static class ConditionalOnIgnoredSubclass { + static class ConditionalOnIgnoredSubclassConfiguration { @Bean @ConditionalOnMissingBean(ignored = CustomExampleBean.class) @@ -617,7 +708,7 @@ class ConditionalOnMissingBeanTests { } @Configuration(proxyBeanMethods = false) - static class ConditionalOnIgnoredSubclassByName { + static class ConditionalOnIgnoredSubclassByNameConfiguration { @Bean @ConditionalOnMissingBean( @@ -683,7 +774,7 @@ class ConditionalOnMissingBeanTests { } @Configuration(proxyBeanMethods = false) - static class NotAutowireCandidateConfig { + static class NotAutowireCandidateConfiguration { @Bean(autowireCandidate = false) String foo() { @@ -693,7 +784,7 @@ class ConditionalOnMissingBeanTests { } @Configuration(proxyBeanMethods = false) - static class NotDefaultCandidateConfig { + static class NotDefaultCandidateConfiguration { @Bean(defaultCandidate = false) String foo() { @@ -704,7 +795,7 @@ class ConditionalOnMissingBeanTests { @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(name = "foo") - static class HierarchyConsidered { + static class HierarchyConsideredConfiguration { @Bean String bar() { @@ -715,7 +806,7 @@ class ConditionalOnMissingBeanTests { @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(name = "foo", search = SearchStrategy.CURRENT) - static class HierarchyNotConsidered { + static class HierarchyNotConsideredConfiguration { @Bean String bar() { @@ -751,7 +842,7 @@ class ConditionalOnMissingBeanTests { } @Configuration(proxyBeanMethods = false) - static class ImpliedOnBeanMethod { + static class ImpliedOnBeanMethodConfiguration { @Bean @ConditionalOnMissingBean @@ -761,6 +852,153 @@ class ConditionalOnMissingBeanTests { } + @Configuration(proxyBeanMethods = false) + static class ParameterizedWithCustomConfiguration { + + @Bean + CustomExampleBean customExampleBean() { + return new CustomExampleBean(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class ParameterizedWithoutCustomConfiguration { + + @Bean + OtherExampleBean otherExampleBean() { + return new OtherExampleBean(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class ParameterizedWithoutCustomContainerConfiguration { + + @Bean + TestParameterizedContainer otherExampleBean() { + return new TestParameterizedContainer<>(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class ParameterizedWithCustomContainerConfiguration { + + @Bean + TestParameterizedContainer customExampleBean() { + return new TestParameterizedContainer<>(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class ParameterizedConditionWithValueConfiguration { + + @Bean + @ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class) + CustomExampleBean conditionalCustomExampleBean() { + return new CustomExampleBean(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class ParameterizedConditionWithReturnTypeConfiguration { + + @Bean + @ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class) + CustomExampleBean conditionalCustomExampleBean() { + return new CustomExampleBean(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class ParameterizedConditionWithReturnRegistrationTypeConfiguration { + + @Bean + @ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class) + TestParameterizedContainer conditionalCustomExampleBean() { + return new TestParameterizedContainer<>(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class AnnotatedNotAutowireCandidateConfiguration { + + @Bean(autowireCandidate = false) + ExampleBean exampleBean() { + return new ExampleBean("value"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class AnnotatedNotDefaultCandidateConfiguration { + + @Bean(autowireCandidate = false) + ExampleBean exampleBean() { + return new ExampleBean("value"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class ParameterizedWithCustomGenericConfiguration { + + @Bean + CustomGenericExampleBean customGenericExampleBean() { + return new CustomGenericExampleBean(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithStringTypeArgumentsConfiguration { + + @Bean + @ConditionalOnMissingBean + GenericExampleBean genericStringExampleBean() { + return new GenericExampleBean<>("genericStringExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class GenericWithIntegerTypeArgumentsConfiguration { + + @Bean + @ConditionalOnMissingBean + GenericExampleBean genericIntegerExampleBean() { + return new GenericExampleBean<>(1_000); + } + + } + + @Configuration(proxyBeanMethods = false) + static class TypeArgumentsConditionWithValueConfiguration { + + @Bean + @ConditionalOnMissingBean(GenericExampleBean.class) + GenericExampleBean genericStringWithValueExampleBean() { + return new GenericExampleBean<>("genericStringWithValueExampleBean"); + } + + } + + @Configuration(proxyBeanMethods = false) + static class TypeArgumentsConditionWithParameterizedContainerConfiguration { + + @Bean + @ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class) + TestParameterizedContainer> parameterizedContainerGenericExampleBean() { + return new TestParameterizedContainer<>(); + } + + } + static class ExampleFactoryBean implements FactoryBean { ExampleFactoryBean(String value) { @@ -807,99 +1045,6 @@ class ConditionalOnMissingBeanTests { } - @Configuration(proxyBeanMethods = false) - static class ParameterizedWithCustomConfig { - - @Bean - CustomExampleBean customExampleBean() { - return new CustomExampleBean(); - } - - } - - @Configuration(proxyBeanMethods = false) - static class ParameterizedWithoutCustomConfig { - - @Bean - OtherExampleBean otherExampleBean() { - return new OtherExampleBean(); - } - - } - - @Configuration(proxyBeanMethods = false) - static class ParameterizedWithoutCustomContainerConfig { - - @Bean - TestParameterizedContainer otherExampleBean() { - return new TestParameterizedContainer<>(); - } - - } - - @Configuration(proxyBeanMethods = false) - static class ParameterizedWithCustomContainerConfig { - - @Bean - TestParameterizedContainer customExampleBean() { - return new TestParameterizedContainer<>(); - } - - } - - @Configuration(proxyBeanMethods = false) - static class ParameterizedConditionWithValueConfig { - - @Bean - @ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class) - CustomExampleBean conditionalCustomExampleBean() { - return new CustomExampleBean(); - } - - } - - @Configuration(proxyBeanMethods = false) - static class ParameterizedConditionWithReturnTypeConfig { - - @Bean - @ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class) - CustomExampleBean conditionalCustomExampleBean() { - return new CustomExampleBean(); - } - - } - - @Configuration(proxyBeanMethods = false) - static class ParameterizedConditionWithReturnRegistrationTypeConfig { - - @Bean - @ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class) - TestParameterizedContainer conditionalCustomExampleBean() { - return new TestParameterizedContainer<>(); - } - - } - - @Configuration(proxyBeanMethods = false) - static class AnnotatedNotAutowireCandidateConfig { - - @Bean(autowireCandidate = false) - ExampleBean exampleBean() { - return new ExampleBean("value"); - } - - } - - @Configuration(proxyBeanMethods = false) - static class AnnotatedNotDefaultCandidateConfig { - - @Bean(autowireCandidate = false) - ExampleBean exampleBean() { - return new ExampleBean("value"); - } - - } - @TestAnnotation static class ExampleBean { @@ -956,6 +1101,30 @@ class ConditionalOnMissingBeanTests { } + @TestAnnotation + static class GenericExampleBean { + + private final T value; + + GenericExampleBean(T value) { + this.value = value; + } + + @Override + public String toString() { + return String.valueOf(this.value); + } + + } + + static class CustomGenericExampleBean extends GenericExampleBean { + + CustomGenericExampleBean() { + super("custom subclass"); + } + + } + @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented