diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunner.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunner.java index b677787584..5350a00731 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunner.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunner.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.function.Function; import java.util.function.Supplier; +import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.boot.context.annotation.Configurations; import org.springframework.boot.context.annotation.UserConfigurations; import org.springframework.boot.test.context.FilteredClassLoader; @@ -32,6 +33,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigRegistry; +import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.ResolvableType; import org.springframework.core.env.Environment; import org.springframework.core.io.DefaultResourceLoader; @@ -156,8 +158,7 @@ public abstract class AbstractApplicationContextRunner initializer) { + public SELF withInitializer(ApplicationContextInitializer initializer) { Assert.notNull(initializer, "Initializer must not be null"); return newInstance(this.contextFactory, add(this.initializers, initializer), this.environmentProperties, this.systemProperties, this.classLoader, @@ -221,6 +222,84 @@ public abstract class AbstractApplicationContextRunner + * Such beans are registered after regular {@linkplain #withUserConfiguration(Class[]) + * user configurations} in the order of registration. + * @param beanType the type of the bean + * @param beanDefinition a supplier for the bean + * @param the type of the bean + * @return a new instance with the updated bean + */ + public SELF withBean(Class beanType, Supplier beanDefinition) { + return withBean(null, beanType, beanDefinition); + } + + /** + * Register the specified user bean with the {@link ApplicationContext}. The bean name + * is generated from the configured {@link BeanNameGenerator} on the underlying + * context. + *

+ * Such beans are registered after regular {@linkplain #withUserConfiguration(Class[]) + * user configurations} in the order of registration. + * @param beanType the type of the bean + * @param beanDefinition a function that accepts the context and return the bean + * @param the type of the bean + * @return a new instance with the updated bean + */ + public SELF withBean(Class beanType, Function beanDefinition) { + return withBean(null, beanType, beanDefinition); + } + + /** + * Register the specified user bean with the {@link ApplicationContext}. If no bean + * name is provided, a default one is generated from the configured + * {@link BeanNameGenerator} on the underlying context. + *

+ * Such beans are registered after regular {@linkplain #withUserConfiguration(Class[]) + * user configurations} in the order of registration. + * @param beanName the name of the bean (may be {@code null}) + * @param beanType the type of the bean + * @param beanDefinition a supplier for the bean + * @param the type of the bean + * @return a new instance with the updated bean + */ + public SELF withBean(String beanName, Class beanType, + Supplier beanDefinition) { + return withBean(beanName, beanType, (context) -> beanDefinition.get()); + } + + /** + * Register the specified user bean with the {@link ApplicationContext}. If no bean + * name is provided, a default one is generated from the configured + * {@link BeanNameGenerator} on the underlying context. + *

+ * Such beans are registered after regular {@linkplain #withUserConfiguration(Class[]) + * user configurations} in the order of registration. + * @param beanName the name of the bean (may be {@code null}) + * @param beanType the type of the bean + * @param beanDefinition a function that accepts the context and return the bean + * @param the type of the bean + * @return a new instance with the updated bean + */ + public SELF withBean(String beanName, Class beanType, + Function beanDefinition) { + return withInitializer( + beanDefinitionRegistrar(beanName, beanType, beanDefinition)); + } + + private ApplicationContextInitializer beanDefinitionRegistrar( + String beanName, Class beanType, Function beanDefinition) { + return (context) -> { + Assert.isInstanceOf(GenericApplicationContext.class, context); + ((GenericApplicationContext) context).registerBean(beanName, beanType, + () -> beanDefinition.apply(context)); + }; + } + /** * Register the specified user configuration classes with the * {@link ApplicationContext}. diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunnerTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunnerTests.java index 5124189044..520f95f970 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunnerTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunnerTests.java @@ -39,6 +39,7 @@ import org.springframework.util.ClassUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIOException; +import static org.assertj.core.api.Assertions.entry; /** * Abstract tests for {@link AbstractApplicationContextRunner} implementations. @@ -136,6 +137,45 @@ public abstract class AbstractApplicationContextRunnerTests assertThat(context).hasBean("foo")); } + @Test + public void runWithUserNamedBeanShouldRegisterBean() { + get().withBean("foo", String.class, () -> "foo") + .run((context) -> assertThat(context).hasBean("foo")); + } + + @Test + public void runWithUserBeanShouldRegisterBeanWithDefaultName() { + get().withBean(String.class, () -> "foo") + .run((context) -> assertThat(context).hasBean("string")); + } + + @Test + public void runWithUserBeanShouldBeRegisteredInOrder() { + get().withBean(String.class, () -> "one").withBean(String.class, () -> "two") + .withBean(String.class, () -> "three").run((context) -> { + assertThat(context).hasBean("string"); + assertThat(context.getBean("string")).isEqualTo("three"); + }); + } + + @Test + public void runWithConfigurationsAndUserBeanShouldRegisterUserBeanLast() { + get().withUserConfiguration(FooConfig.class) + .withBean("foo", String.class, () -> "overridden").run((context) -> { + assertThat(context).hasBean("foo"); + assertThat(context.getBean("foo")).isEqualTo("overridden"); + }); + } + + @Test + public void runWithUserBeanShouldHaveAccessToContext() { + get().withUserConfiguration(FooConfig.class) + .withBean(String.class, (context) -> "Result: " + context.getBean("foo")) + .run((context) -> assertThat(context.getBeansOfType(String.class)) + .containsOnly(entry("foo", "foo"), + entry("string", "Result: foo"))); + } + @Test public void runWithMultipleConfigurationsShouldRegisterAllConfigurations() { get().withUserConfiguration(FooConfig.class)