Skip to content

Commit

Permalink
Allow ApplicationContextRunner to accept simple bean definitions
Browse files Browse the repository at this point in the history
This commit adds `withBean` methods to the `ApplicationContextRunner`
abstraction so that simple beans can be registered inline. This is a
nice alternative for cases where a inner configuration class has to be
defined for the purpose of creating a simple bean.

Closes spring-projectsgh-16011
  • Loading branch information
snicoll committed Apr 9, 2019
1 parent 7054a33 commit a780875
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
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;
Expand All @@ -32,6 +33,7 @@
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;
Expand Down Expand Up @@ -156,8 +158,7 @@ protected AbstractApplicationContextRunner(Supplier<C> contextFactory,
* @param initializer the initializer to add
* @return a new instance with the updated initializers
*/
public SELF withInitializer(
ApplicationContextInitializer<? super ConfigurableApplicationContext> initializer) {
public SELF withInitializer(ApplicationContextInitializer<? super C> initializer) {
Assert.notNull(initializer, "Initializer must not be null");
return newInstance(this.contextFactory, add(this.initializers, initializer),
this.environmentProperties, this.systemProperties, this.classLoader,
Expand Down Expand Up @@ -221,6 +222,84 @@ public SELF withParent(ApplicationContext parent) {
parent, this.configurations);
}

/**
* Register the specified user bean with the {@link ApplicationContext}. The bean name
* is generated from the configured {@link BeanNameGenerator} on the underlying
* context.
* <p>
* 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 <T> the type of the bean
* @return a new instance with the updated bean
*/
public <T> SELF withBean(Class<T> beanType, Supplier<T> 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.
* <p>
* 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 <T> the type of the bean
* @return a new instance with the updated bean
*/
public <T> SELF withBean(Class<T> beanType, Function<? super C, T> 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.
* <p>
* 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 <T> the type of the bean
* @return a new instance with the updated bean
*/
public <T> SELF withBean(String beanName, Class<T> beanType,
Supplier<T> 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.
* <p>
* 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 <T> the type of the bean
* @return a new instance with the updated bean
*/
public <T> SELF withBean(String beanName, Class<T> beanType,
Function<? super C, T> beanDefinition) {
return withInitializer(
beanDefinitionRegistrar(beanName, beanType, beanDefinition));
}

private <T> ApplicationContextInitializer<? super C> beanDefinitionRegistrar(
String beanName, Class<T> beanType, Function<? super C, T> 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}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
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.
Expand Down Expand Up @@ -136,6 +137,45 @@ public void runWithConfigurationsShouldRegisterConfigurations() {
.run((context) -> 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)
Expand Down

0 comments on commit a780875

Please sign in to comment.