Make ApplicationContextRunner immutable

Update `ApplicationContextRunner` so that it is totally immutable.
Methods now return new instances rather than changing existing state.

See gh-9875
pull/7499/merge
Phillip Webb 7 years ago
parent ad9f28110c
commit 89ad0660d1

@ -231,15 +231,15 @@ public class ArtemisAutoConfigurationTests {
"spring.artemis.embedded.dataDirectory:" "spring.artemis.embedded.dataDirectory:"
+ dataFolder.getAbsolutePath()) + dataFolder.getAbsolutePath())
.run((context) -> context.getBean(JmsTemplate.class).send("TestQueue", .run((context) -> context.getBean(JmsTemplate.class).send("TestQueue",
(session) -> session.createTextMessage(messageId))); (session) -> session.createTextMessage(messageId)))
// Start the server again and check if our message is still here .run((context) -> {
this.contextRunner.run((context) -> { // Start the server again and check if our message is still here
JmsTemplate jmsTemplate2 = context.getBean(JmsTemplate.class); JmsTemplate jmsTemplate2 = context.getBean(JmsTemplate.class);
jmsTemplate2.setReceiveTimeout(1000L); jmsTemplate2.setReceiveTimeout(1000L);
Message message = jmsTemplate2.receive("TestQueue"); Message message = jmsTemplate2.receive("TestQueue");
assertThat(message).isNotNull(); assertThat(message).isNotNull();
assertThat(((TextMessage) message).getText()).isEqualTo(messageId); assertThat(((TextMessage) message).getText()).isEqualTo(messageId);
}); });
} }
@Test @Test

@ -55,7 +55,9 @@ public class TestDatabaseAutoConfigurationTests {
DataSource datasource = context.getBean(DataSource.class); DataSource datasource = context.getBean(DataSource.class);
JdbcTemplate jdbcTemplate = new JdbcTemplate(datasource); JdbcTemplate jdbcTemplate = new JdbcTemplate(datasource);
jdbcTemplate.execute("create table example (id int, name varchar);"); jdbcTemplate.execute("create table example (id int, name varchar);");
this.contextRunner.run((secondContext) -> { this.contextRunner
.withUserConfiguration(ExistingDataSourceConfiguration.class)
.run((secondContext) -> {
DataSource anotherDatasource = secondContext DataSource anotherDatasource = secondContext
.getBean(DataSource.class); .getBean(DataSource.class);
JdbcTemplate anotherJdbcTemplate = new JdbcTemplate( JdbcTemplate anotherJdbcTemplate = new JdbcTemplate(

@ -17,6 +17,7 @@
package org.springframework.boot.test.context.runner; package org.springframework.boot.test.context.runner;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -98,25 +99,48 @@ abstract class AbstractApplicationContextRunner<SELF extends AbstractApplication
private final Supplier<C> contextFactory; private final Supplier<C> contextFactory;
private TestPropertyValues environmentProperties; private final TestPropertyValues environmentProperties;
private TestPropertyValues systemProperties; private final TestPropertyValues systemProperties;
private ClassLoader classLoader; private final ClassLoader classLoader;
private ApplicationContext parent; private final ApplicationContext parent;
private final List<Configurations> configurations = new ArrayList<>(); private final List<Configurations> configurations;
/** /**
* Create a new {@link AbstractApplicationContextRunner} instance. * Create a new {@link AbstractApplicationContextRunner} instance.
* @param contextFactory the factory used to create the actual context * @param contextFactory the factory used to create the actual context
*/ */
protected AbstractApplicationContextRunner(Supplier<C> contextFactory) { protected AbstractApplicationContextRunner(Supplier<C> contextFactory) {
this(contextFactory, TestPropertyValues.empty(), TestPropertyValues.empty(), null,
null, Collections.emptyList());
}
/**
* Create a new {@link AbstractApplicationContextRunner} instance.
* @param contextFactory the factory used to create the actual context
* @param environmentProperties the environment properties
* @param systemProperties the system properties
* @param classLoader the class loader
* @param parent the parent
* @param configurations the configuration
*/
protected AbstractApplicationContextRunner(Supplier<C> contextFactory,
TestPropertyValues environmentProperties, TestPropertyValues systemProperties,
ClassLoader classLoader, ApplicationContext parent,
List<Configurations> configurations) {
Assert.notNull(contextFactory, "ContextFactory must not be null"); Assert.notNull(contextFactory, "ContextFactory must not be null");
Assert.notNull(environmentProperties, "EnvironmentProperties must not be null");
Assert.notNull(systemProperties, "SystemProperties must not be null");
Assert.notNull(configurations, "Configurations must not be null");
this.contextFactory = contextFactory; this.contextFactory = contextFactory;
this.environmentProperties = TestPropertyValues.empty(); this.environmentProperties = environmentProperties;
this.systemProperties = TestPropertyValues.empty(); this.systemProperties = systemProperties;
this.classLoader = classLoader;
this.parent = parent;
this.configurations = Collections.unmodifiableList(configurations);
} }
/** /**
@ -125,13 +149,14 @@ abstract class AbstractApplicationContextRunner<SELF extends AbstractApplication
* might have been specified previously. * might have been specified previously.
* @param pairs the key-value pairs for properties that need to be added to the * @param pairs the key-value pairs for properties that need to be added to the
* environment * environment
* @return this instance * @return a new instance with the updated property values
* @see TestPropertyValues * @see TestPropertyValues
* @see #withSystemProperties(String...) * @see #withSystemProperties(String...)
*/ */
public SELF withPropertyValues(String... pairs) { public SELF withPropertyValues(String... pairs) {
this.environmentProperties = this.environmentProperties.and(pairs); return newInstance(this.contextFactory, this.environmentProperties.and(pairs),
return self(); this.systemProperties, this.classLoader, this.parent,
this.configurations);
} }
/** /**
@ -140,13 +165,14 @@ abstract class AbstractApplicationContextRunner<SELF extends AbstractApplication
* context is {@link #run(ContextConsumer) run} and restored when the context is * context is {@link #run(ContextConsumer) run} and restored when the context is
* closed. * closed.
* @param pairs the key-value pairs for properties that need to be added to the system * @param pairs the key-value pairs for properties that need to be added to the system
* @return this instance * @return a new instance with the updated system properties
* @see TestPropertyValues * @see TestPropertyValues
* @see #withSystemProperties(String...) * @see #withSystemProperties(String...)
*/ */
public SELF withSystemProperties(String... pairs) { public SELF withSystemProperties(String... pairs) {
this.systemProperties = this.systemProperties.and(pairs); return newInstance(this.contextFactory, this.environmentProperties,
return self(); this.systemProperties.and(pairs), this.classLoader, this.parent,
this.configurations);
} }
/** /**
@ -154,30 +180,30 @@ abstract class AbstractApplicationContextRunner<SELF extends AbstractApplication
* Customizing the {@link ClassLoader} is an effective manner to hide resources from * Customizing the {@link ClassLoader} is an effective manner to hide resources from
* the classpath. * the classpath.
* @param classLoader the classloader to use (can be null to use the default) * @param classLoader the classloader to use (can be null to use the default)
* @return this instance * @return a new instance with the updated class loader
* @see HidePackagesClassLoader * @see HidePackagesClassLoader
*/ */
public SELF withClassLoader(ClassLoader classLoader) { public SELF withClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader; return newInstance(this.contextFactory, this.environmentProperties,
return self(); this.systemProperties, classLoader, this.parent, this.configurations);
} }
/** /**
* Configure the {@link ConfigurableApplicationContext#setParent(ApplicationContext) * Configure the {@link ConfigurableApplicationContext#setParent(ApplicationContext)
* parent} of the {@link ApplicationContext}. * parent} of the {@link ApplicationContext}.
* @param parent the parent * @param parent the parent
* @return this instance * @return a new instance with the updated parent
*/ */
public SELF withParent(ApplicationContext parent) { public SELF withParent(ApplicationContext parent) {
this.parent = parent; return newInstance(this.contextFactory, this.environmentProperties,
return self(); this.systemProperties, this.classLoader, parent, this.configurations);
} }
/** /**
* Register the specified user configuration classes with the * Register the specified user configuration classes with the
* {@link ApplicationContext}. * {@link ApplicationContext}.
* @param configurationClasses the user configuration classes to add * @param configurationClasses the user configuration classes to add
* @return this instance * @return a new instance with the updated configuration
*/ */
public SELF withUserConfiguration(Class<?>... configurationClasses) { public SELF withUserConfiguration(Class<?>... configurationClasses) {
return withConfiguration(UserConfigurations.of(configurationClasses)); return withConfiguration(UserConfigurations.of(configurationClasses));
@ -186,32 +212,42 @@ abstract class AbstractApplicationContextRunner<SELF extends AbstractApplication
/** /**
* Register the specified configuration classes with the {@link ApplicationContext}. * Register the specified configuration classes with the {@link ApplicationContext}.
* @param configurations the configurations to add * @param configurations the configurations to add
* @return this instance * @return a new instance with the updated configuration
*/ */
public SELF withConfiguration(Configurations configurations) { public SELF withConfiguration(Configurations configurations) {
Assert.notNull(configurations, "Configurations must not be null"); Assert.notNull(configurations, "Configurations must not be null");
this.configurations.add(configurations); return newInstance(this.contextFactory, this.environmentProperties,
return self(); this.systemProperties, this.classLoader, this.parent,
add(this.configurations, configurations));
} }
@SuppressWarnings("unchecked") private <T> List<T> add(List<T> list, T element) {
protected final SELF self() { List<T> result = new ArrayList<>(list);
return (SELF) this; result.add(element);
return result;
} }
protected abstract SELF newInstance(Supplier<C> contextFactory,
TestPropertyValues environmentProperties, TestPropertyValues systemProperties,
ClassLoader classLoader, ApplicationContext parent,
List<Configurations> configurations);
/** /**
* Create and refresh a new {@link ApplicationContext} based on the current state of * Create and refresh a new {@link ApplicationContext} based on the current state of
* this loader. The context is consumed by the specified {@code consumer} and closed * this loader. The context is consumed by the specified {@code consumer} and closed
* upon completion. * upon completion.
* @param consumer the consumer of the created {@link ApplicationContext} * @param consumer the consumer of the created {@link ApplicationContext}
* @return this instance
*/ */
public void run(ContextConsumer<? super A> consumer) { @SuppressWarnings("unchecked")
public SELF run(ContextConsumer<? super A> consumer) {
this.systemProperties.applyToSystemProperties(() -> { this.systemProperties.applyToSystemProperties(() -> {
try (A context = createAssertableContext()) { try (A context = createAssertableContext()) {
accept(consumer, context); accept(consumer, context);
} }
return null; return null;
}); });
return (SELF) this;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")

@ -16,9 +16,13 @@
package org.springframework.boot.test.context.runner; package org.springframework.boot.test.context.runner;
import java.util.List;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.springframework.boot.context.annotation.Configurations;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@ -54,4 +58,23 @@ public class ApplicationContextRunner extends
super(contextFactory); super(contextFactory);
} }
private ApplicationContextRunner(
Supplier<ConfigurableApplicationContext> contextFactory,
TestPropertyValues environmentProperties, TestPropertyValues systemProperties,
ClassLoader classLoader, ApplicationContext parent,
List<Configurations> configurations) {
super(contextFactory, environmentProperties, systemProperties, classLoader,
parent, configurations);
}
@Override
protected ApplicationContextRunner newInstance(
Supplier<ConfigurableApplicationContext> contextFactory,
TestPropertyValues environmentProperties, TestPropertyValues systemProperties,
ClassLoader classLoader, ApplicationContext parent,
List<Configurations> configurations) {
return new ApplicationContextRunner(contextFactory, environmentProperties,
systemProperties, classLoader, parent, configurations);
}
} }

@ -16,11 +16,15 @@
package org.springframework.boot.test.context.runner; package org.springframework.boot.test.context.runner;
import java.util.List;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.springframework.boot.context.annotation.Configurations;
import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext; import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.web.reactive.context.ConfigurableReactiveWebApplicationContext; import org.springframework.boot.web.reactive.context.ConfigurableReactiveWebApplicationContext;
import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext; import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext;
import org.springframework.context.ApplicationContext;
/** /**
* A {@link AbstractApplicationContextRunner ApplicationContext runner} for a * A {@link AbstractApplicationContextRunner ApplicationContext runner} for a
@ -54,4 +58,24 @@ public final class ReactiveWebApplicationContextRunner extends
super(contextFactory); super(contextFactory);
} }
private ReactiveWebApplicationContextRunner(
Supplier<ConfigurableReactiveWebApplicationContext> contextFactory,
TestPropertyValues environmentProperties, TestPropertyValues systemProperties,
ClassLoader classLoader, ApplicationContext parent,
List<Configurations> configurations) {
super(contextFactory, environmentProperties, systemProperties, classLoader,
parent, configurations);
}
@Override
protected ReactiveWebApplicationContextRunner newInstance(
Supplier<ConfigurableReactiveWebApplicationContext> contextFactory,
TestPropertyValues environmentProperties, TestPropertyValues systemProperties,
ClassLoader classLoader, ApplicationContext parent,
List<Configurations> configurations) {
return new ReactiveWebApplicationContextRunner(contextFactory,
environmentProperties, systemProperties, classLoader, parent,
configurations);
}
} }

@ -16,9 +16,13 @@
package org.springframework.boot.test.context.runner; package org.springframework.boot.test.context.runner;
import java.util.List;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.springframework.boot.context.annotation.Configurations;
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ApplicationContext;
import org.springframework.mock.web.MockServletContext; import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
@ -58,6 +62,25 @@ public final class WebApplicationContextRunner extends
super(contextFactory); super(contextFactory);
} }
private WebApplicationContextRunner(
Supplier<ConfigurableWebApplicationContext> contextFactory,
TestPropertyValues environmentProperties, TestPropertyValues systemProperties,
ClassLoader classLoader, ApplicationContext parent,
List<Configurations> configurations) {
super(contextFactory, environmentProperties, systemProperties, classLoader,
parent, configurations);
}
@Override
protected WebApplicationContextRunner newInstance(
Supplier<ConfigurableWebApplicationContext> contextFactory,
TestPropertyValues environmentProperties, TestPropertyValues systemProperties,
ClassLoader classLoader, ApplicationContext parent,
List<Configurations> configurations) {
return new WebApplicationContextRunner(contextFactory, environmentProperties,
systemProperties, classLoader, parent, configurations);
}
/** /**
* Decorate the specified {@code contextFactory} to set a {@link MockServletContext} * Decorate the specified {@code contextFactory} to set a {@link MockServletContext}
* on each newly created {@link WebApplicationContext}. * on each newly created {@link WebApplicationContext}.

Loading…
Cancel
Save