001/*002 * Copyright 2012-2018 the original author or authors.003 *004 * Licensed under the Apache License, Version 2.0 (the "License");005 * you may not use this file except in compliance with the License.006 * You may obtain a copy of the License at007 *008 * http://www.apache.org/licenses/LICENSE-2.0009 *010 * Unless required by applicable law or agreed to in writing, software011 * distributed under the License is distributed on an "AS IS" BASIS,012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.013 * See the License for the specific language governing permissions and014 * limitations under the License.015 */016017package org.springframework.boot.test.context.runner;018019import java.util.ArrayList;020import java.util.Collections;021import java.util.List;022import java.util.function.Function;023import java.util.function.Supplier;024025import org.springframework.boot.context.annotation.Configurations;026import org.springframework.boot.context.annotation.UserConfigurations;027import org.springframework.boot.test.context.FilteredClassLoader;028import org.springframework.boot.test.context.assertj.ApplicationContextAssert;029import org.springframework.boot.test.context.assertj.ApplicationContextAssertProvider;030import org.springframework.boot.test.util.TestPropertyValues;031import org.springframework.context.ApplicationContext;032import org.springframework.context.ApplicationContextInitializer;033import org.springframework.context.ConfigurableApplicationContext;034import org.springframework.context.annotation.AnnotationConfigRegistry;035import org.springframework.core.ResolvableType;036import org.springframework.core.env.Environment;037import org.springframework.core.io.DefaultResourceLoader;038import org.springframework.util.Assert;039040/**041 * Utility design to run and an {@link ApplicationContext} and provide AssertJ style042 * assertions. The test is best used as a field of a test class, describing the shared043 * configuration required for the test:044 *045 * <pre class="code">046 * public class MyContextTests {047 * private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()048 * .withPropertyValues("spring.foo=bar")049 * .withUserConfiguration(MyConfiguration.class);050 * }</pre>051 *052 * <p>053 * The initialization above makes sure to register {@code MyConfiguration} for all tests054 * and set the {@code spring.foo} property to {@code bar} unless specified otherwise.055 * <p>056 * Based on the configuration above, a specific test can simulate what will happen when057 * the context runs, perhaps with overridden property values:058 *059 * <pre class="code">060 * &#064;Test061 * public someTest() {062 * this.contextRunner.withPropertyValues("spring.foo=biz").run((context) -&gt; {063 * assertThat(context).containsSingleBean(MyBean.class);064 * // other assertions065 * });066 * }</pre>067 * <p>068 * The test above has changed the {@code spring.foo} property to {@code biz} and is069 * asserting that the context contains a single {@code MyBean} bean. The070 * {@link #run(ContextConsumer) run} method takes a {@link ContextConsumer} that can apply071 * assertions to the context. Upon completion, the context is automatically closed.072 * <p>073 * If the application context fails to start the {@code #run(ContextConsumer)} method is074 * called with a "failed" application context. Calls to the context will throw an075 * {@link IllegalStateException} and assertions that expect a running context will fail.076 * The {@link ApplicationContextAssert#getFailure() getFailure()} assertion can be used if077 * further checks are required on the cause of the failure: <pre class="code">078 * &#064;Test079 * public someTest() {080 * this.context.withPropertyValues("spring.foo=fails").run((loaded) -&gt; {081 * assertThat(loaded).getFailure().hasCauseInstanceOf(BadPropertyException.class);082 * // other assertions083 * });084 * }</pre>085 * <p>086 *087 * @param <SELF> the "self" type for this runner088 * @param <C> the context type089 * @param <A> the application context assertion provider090 * @author Stephane Nicoll091 * @author Andy Wilkinson092 * @author Phillip Webb093 * @since 2.0.0094 * @see ApplicationContextRunner095 * @see WebApplicationContextRunner096 * @see ReactiveWebApplicationContextRunner097 * @see ApplicationContextAssert098 */099public abstract class AbstractApplicationContextRunner<SELF extends AbstractApplicationContextRunner<SELF, C, A>, C extends ConfigurableApplicationContext, A extends ApplicationContextAssertProvider<C>> {100101 private final Supplier<C> contextFactory;102103 private final List<ApplicationContextInitializer<? super C>> initializers;104105 private final TestPropertyValues environmentProperties;106107 private final TestPropertyValues systemProperties;108109 private final ClassLoader classLoader;110111 private final ApplicationContext parent;112113 private final List<Configurations> configurations;114115 /**116 * Create a new {@link AbstractApplicationContextRunner} instance.117 * @param contextFactory the factory used to create the actual context118 */119 protected AbstractApplicationContextRunner(Supplier<C> contextFactory) {120 this(contextFactory, Collections.emptyList(), TestPropertyValues.empty(),121 TestPropertyValues.empty(), null, null, Collections.emptyList());122 }123124 /**125 * Create a new {@link AbstractApplicationContextRunner} instance.126 * @param contextFactory the factory used to create the actual context127 * @param initializers the initializers128 * @param environmentProperties the environment properties129 * @param systemProperties the system properties130 * @param classLoader the class loader131 * @param parent the parent132 * @param configurations the configuration133 */134 protected AbstractApplicationContextRunner(Supplier<C> contextFactory,135 List<ApplicationContextInitializer<? super C>> initializers,136 TestPropertyValues environmentProperties, TestPropertyValues systemProperties,137 ClassLoader classLoader, ApplicationContext parent,138 List<Configurations> configurations) {139 Assert.notNull(contextFactory, "ContextFactory must not be null");140 Assert.notNull(environmentProperties, "EnvironmentProperties must not be null");141 Assert.notNull(systemProperties, "SystemProperties must not be null");142 Assert.notNull(configurations, "Configurations must not be null");143 Assert.notNull(initializers, "Initializers must not be null");144 this.contextFactory = contextFactory;145 this.initializers = Collections.unmodifiableList(initializers);146 this.environmentProperties = environmentProperties;147 this.systemProperties = systemProperties;148 this.classLoader = classLoader;149 this.parent = parent;150 this.configurations = Collections.unmodifiableList(configurations);151 }152153 /**154 * Add a {@link ApplicationContextInitializer} to be called when the context is155 * created.156 * @param initializer the initializer to add157 * @return a new instance with the updated initializers158 */159 public SELF withInitializer(160 ApplicationContextInitializer<? super ConfigurableApplicationContext> initializer) {161 Assert.notNull(initializer, "Initializer must not be null");162 return newInstance(this.contextFactory, add(this.initializers, initializer),163 this.environmentProperties, this.systemProperties, this.classLoader,164 this.parent, this.configurations);165 }166167 /**168 * Add the specified {@link Environment} property pairs. Key-value pairs can be169 * specified with colon (":") or equals ("=") separators. Override matching keys that170 * might have been specified previously.171 * @param pairs the key-value pairs for properties that need to be added to the172 * environment173 * @return a new instance with the updated property values174 * @see TestPropertyValues175 * @see #withSystemProperties(String...)176 */177 public SELF withPropertyValues(String... pairs) {178 return newInstance(this.contextFactory, this.initializers,179 this.environmentProperties.and(pairs), this.systemProperties,180 this.classLoader, this.parent, this.configurations);181 }182183 /**184 * Add the specified {@link System} property pairs. Key-value pairs can be specified185 * with colon (":") or equals ("=") separators. System properties are added before the186 * context is {@link #run(ContextConsumer) run} and restored when the context is187 * closed.188 * @param pairs the key-value pairs for properties that need to be added to the system189 * @return a new instance with the updated system properties190 * @see TestPropertyValues191 * @see #withSystemProperties(String...)192 */193 public SELF withSystemProperties(String... pairs) {194 return newInstance(this.contextFactory, this.initializers,195 this.environmentProperties, this.systemProperties.and(pairs),196 this.classLoader, this.parent, this.configurations);197 }198199 /**200 * Customize the {@link ClassLoader} that the {@link ApplicationContext} should use201 * for resource loading and bean class loading.202 * @param classLoader the classloader to use (can be null to use the default)203 * @return a new instance with the updated class loader204 * @see FilteredClassLoader205 */206 public SELF withClassLoader(ClassLoader classLoader) {207 return newInstance(this.contextFactory, this.initializers,208 this.environmentProperties, this.systemProperties, classLoader,209 this.parent, this.configurations);210 }211212 /**213 * Configure the {@link ConfigurableApplicationContext#setParent(ApplicationContext)214 * parent} of the {@link ApplicationContext}.215 * @param parent the parent216 * @return a new instance with the updated parent217 */218 public SELF withParent(ApplicationContext parent) {219 return newInstance(this.contextFactory, this.