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:"
+ dataFolder.getAbsolutePath())
.run((context) -> context.getBean(JmsTemplate.class).send("TestQueue",
(session) -> session.createTextMessage(messageId)));
// Start the server again and check if our message is still here
this.contextRunner.run((context) -> {
JmsTemplate jmsTemplate2 = context.getBean(JmsTemplate.class);
jmsTemplate2.setReceiveTimeout(1000L);
Message message = jmsTemplate2.receive("TestQueue");
assertThat(message).isNotNull();
assertThat(((TextMessage) message).getText()).isEqualTo(messageId);
});
(session) -> session.createTextMessage(messageId)))
.run((context) -> {
// Start the server again and check if our message is still here
JmsTemplate jmsTemplate2 = context.getBean(JmsTemplate.class);
jmsTemplate2.setReceiveTimeout(1000L);
Message message = jmsTemplate2.receive("TestQueue");
assertThat(message).isNotNull();
assertThat(((TextMessage) message).getText()).isEqualTo(messageId);
});
}
@Test

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

@ -17,6 +17,7 @@
package org.springframework.boot.test.context.runner;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
@ -98,25 +99,48 @@ abstract class AbstractApplicationContextRunner<SELF extends AbstractApplication
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.
* @param contextFactory the factory used to create the actual context
*/
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(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.environmentProperties = TestPropertyValues.empty();
this.systemProperties = TestPropertyValues.empty();
this.environmentProperties = environmentProperties;
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.
* @param pairs the key-value pairs for properties that need to be added to the
* environment
* @return this instance
* @return a new instance with the updated property values
* @see TestPropertyValues
* @see #withSystemProperties(String...)
*/
public SELF withPropertyValues(String... pairs) {
this.environmentProperties = this.environmentProperties.and(pairs);
return self();
return newInstance(this.contextFactory, this.environmentProperties.and(pairs),
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
* closed.
* @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 #withSystemProperties(String...)
*/
public SELF withSystemProperties(String... pairs) {
this.systemProperties = this.systemProperties.and(pairs);
return self();
return newInstance(this.contextFactory, this.environmentProperties,
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
* the classpath.
* @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
*/
public SELF withClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
return self();
return newInstance(this.contextFactory, this.environmentProperties,
this.systemProperties, classLoader, this.parent, this.configurations);
}
/**
* Configure the {@link ConfigurableApplicationContext#setParent(ApplicationContext)
* parent} of the {@link ApplicationContext}.
* @param parent the parent
* @return this instance
* @return a new instance with the updated parent
*/
public SELF withParent(ApplicationContext parent) {
this.parent = parent;
return self();
return newInstance(this.contextFactory, this.environmentProperties,
this.systemProperties, this.classLoader, parent, this.configurations);
}
/**
* Register the specified user configuration classes with the
* {@link ApplicationContext}.
* @param configurationClasses the user configuration classes to add
* @return this instance
* @return a new instance with the updated configuration
*/
public SELF withUserConfiguration(Class<?>... 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}.
* @param configurations the configurations to add
* @return this instance
* @return a new instance with the updated configuration
*/
public SELF withConfiguration(Configurations configurations) {
Assert.notNull(configurations, "Configurations must not be null");
this.configurations.add(configurations);
return self();
return newInstance(this.contextFactory, this.environmentProperties,
this.systemProperties, this.classLoader, this.parent,
add(this.configurations, configurations));
}
@SuppressWarnings("unchecked")
protected final SELF self() {
return (SELF) this;
private <T> List<T> add(List<T> list, T element) {
List<T> result = new ArrayList<>(list);
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
* this loader. The context is consumed by the specified {@code consumer} and closed
* upon completion.
* @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(() -> {
try (A context = createAssertableContext()) {
accept(consumer, context);
}
return null;
});
return (SELF) this;
}
@SuppressWarnings("unchecked")

@ -16,9 +16,13 @@
package org.springframework.boot.test.context.runner;
import java.util.List;
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.util.TestPropertyValues;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@ -54,4 +58,23 @@ public class ApplicationContextRunner extends
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;
import java.util.List;
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.util.TestPropertyValues;
import org.springframework.boot.web.reactive.context.ConfigurableReactiveWebApplicationContext;
import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext;
import org.springframework.context.ApplicationContext;
/**
* A {@link AbstractApplicationContextRunner ApplicationContext runner} for a
@ -54,4 +58,24 @@ public final class ReactiveWebApplicationContextRunner extends
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;
import java.util.List;
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.util.TestPropertyValues;
import org.springframework.context.ApplicationContext;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.WebApplicationContext;
@ -58,6 +62,25 @@ public final class WebApplicationContextRunner extends
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}
* on each newly created {@link WebApplicationContext}.

Loading…
Cancel
Save