All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.boot.test.context.runner.AbstractApplicationContextRunner Maven / Gradle / Ivy

There is a newer version: 3.2.5
Show newest version
/*
 * Copyright 2012-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.test.context.runner;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;

import org.springframework.boot.context.annotation.Configurations;
import org.springframework.boot.context.annotation.UserConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.assertj.ApplicationContextAssert;
import org.springframework.boot.test.context.assertj.ApplicationContextAssertProvider;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigRegistry;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.util.Assert;

/**
 * Utility design to run and an {@link ApplicationContext} and provide AssertJ style
 * assertions. The test is best used as a field of a test class, describing the shared
 * configuration required for the test:
 *
 * 
 * public class MyContextTests {
 *     private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
 *             .withPropertyValues("spring.foo=bar")
 *             .withUserConfiguration(MyConfiguration.class);
 * }
* *

* The initialization above makes sure to register {@code MyConfiguration} for all tests * and set the {@code spring.foo} property to {@code bar} unless specified otherwise. *

* Based on the configuration above, a specific test can simulate what will happen when * the context runs, perhaps with overridden property values: * *

 * @Test
 * public someTest() {
 *     this.contextRunner.withPropertyValues("spring.foo=biz").run((context) -> {
 *         assertThat(context).containsSingleBean(MyBean.class);
 *         // other assertions
 *     });
 * }
*

* The test above has changed the {@code spring.foo} property to {@code biz} and is * asserting that the context contains a single {@code MyBean} bean. The * {@link #run(ContextConsumer) run} method takes a {@link ContextConsumer} that can apply * assertions to the context. Upon completion, the context is automatically closed. *

* If the application context fails to start the {@code #run(ContextConsumer)} method is * called with a "failed" application context. Calls to the context will throw an * {@link IllegalStateException} and assertions that expect a running context will fail. * The {@link ApplicationContextAssert#getFailure() getFailure()} assertion can be used if * further checks are required on the cause of the failure:

 * @Test
 * public someTest() {
 *     this.context.withPropertyValues("spring.foo=fails").run((loaded) -> {
 *         assertThat(loaded).getFailure().hasCauseInstanceOf(BadPropertyException.class);
 *         // other assertions
 *     });
 * }
*

* * @param the "self" type for this runner * @param the context type * @param the application context assertion provider * @author Stephane Nicoll * @author Andy Wilkinson * @author Phillip Webb * @since 2.0.0 * @see ApplicationContextRunner * @see WebApplicationContextRunner * @see ReactiveWebApplicationContextRunner * @see ApplicationContextAssert */ public abstract class AbstractApplicationContextRunner, C extends ConfigurableApplicationContext, A extends ApplicationContextAssertProvider> { private final Supplier contextFactory; private final List> initializers; private final TestPropertyValues environmentProperties; private final TestPropertyValues systemProperties; private final ClassLoader classLoader; private final ApplicationContext parent; private final List configurations; /** * Create a new {@link AbstractApplicationContextRunner} instance. * @param contextFactory the factory used to create the actual context */ protected AbstractApplicationContextRunner(Supplier contextFactory) { this(contextFactory, Collections.emptyList(), 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 initializers the initializers * @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 contextFactory, List> initializers, TestPropertyValues environmentProperties, TestPropertyValues systemProperties, ClassLoader classLoader, ApplicationContext parent, List 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"); Assert.notNull(initializers, "Initializers must not be null"); this.contextFactory = contextFactory; this.initializers = Collections.unmodifiableList(initializers); this.environmentProperties = environmentProperties; this.systemProperties = systemProperties; this.classLoader = classLoader; this.parent = parent; this.configurations = Collections.unmodifiableList(configurations); } /** * Add a {@link ApplicationContextInitializer} to be called when the context is * created. * @param initializer the initializer to add * @return a new instance with the updated initializers */ 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, this.parent, this.configurations); } /** * Add the specified {@link Environment} property pairs. Key-value pairs can be * specified with colon (":") or equals ("=") separators. Override matching keys that * might have been specified previously. * @param pairs the key-value pairs for properties that need to be added to the * environment * @return a new instance with the updated property values * @see TestPropertyValues * @see #withSystemProperties(String...) */ public SELF withPropertyValues(String... pairs) { return newInstance(this.contextFactory, this.initializers, this.environmentProperties.and(pairs), this.systemProperties, this.classLoader, this.parent, this.configurations); } /** * Add the specified {@link System} property pairs. Key-value pairs can be specified * with colon (":") or equals ("=") separators. System properties are added before the * 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 a new instance with the updated system properties * @see TestPropertyValues * @see #withSystemProperties(String...) */ public SELF withSystemProperties(String... pairs) { return newInstance(this.contextFactory, this.initializers, this.environmentProperties, this.systemProperties.and(pairs), this.classLoader, this.parent, this.configurations); } /** * Customize the {@link ClassLoader} that the {@link ApplicationContext} should use * for resource loading and bean class loading. * @param classLoader the classloader to use (can be null to use the default) * @return a new instance with the updated class loader * @see FilteredClassLoader */ public SELF withClassLoader(ClassLoader classLoader) { return newInstance(this.contextFactory, this.initializers, 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 a new instance with the updated parent */ public SELF withParent(ApplicationContext parent) { return newInstance(this.contextFactory, this.initializers, 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 a new instance with the updated configuration */ public SELF withUserConfiguration(Class... configurationClasses) { return withConfiguration(UserConfigurations.of(configurationClasses)); } /** * Register the specified configuration classes with the {@link ApplicationContext}. * @param configurations the configurations to add * @return a new instance with the updated configuration */ public SELF withConfiguration(Configurations configurations) { Assert.notNull(configurations, "Configurations must not be null"); return newInstance(this.contextFactory, this.initializers, this.environmentProperties, this.systemProperties, this.classLoader, this.parent, add(this.configurations, configurations)); } /** * Apply customization to this runner. * @param customizer the customizer to call * @return a new instance with the customizations applied */ @SuppressWarnings("unchecked") public SELF with(Function customizer) { return customizer.apply((SELF) this); } private List add(List list, T element) { List result = new ArrayList<>(list); result.add(element); return result; } protected abstract SELF newInstance(Supplier contextFactory, List> initializers, TestPropertyValues environmentProperties, TestPropertyValues systemProperties, ClassLoader classLoader, ApplicationContext parent, List 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 */ @SuppressWarnings("unchecked") public SELF run(ContextConsumer consumer) { withContextClassLoader(this.classLoader, () -> { this.systemProperties.applyToSystemProperties(() -> { try (A context = createAssertableContext()) { accept(consumer, context); } return null; }); }); return (SELF) this; } private void withContextClassLoader(ClassLoader classLoader, Runnable action) { if (classLoader == null) { action.run(); } else { Thread currentThread = Thread.currentThread(); ClassLoader previous = currentThread.getContextClassLoader(); currentThread.setContextClassLoader(classLoader); try { action.run(); } finally { currentThread.setContextClassLoader(previous); } } } @SuppressWarnings("unchecked") private A createAssertableContext() { ResolvableType resolvableType = ResolvableType .forClass(AbstractApplicationContextRunner.class, getClass()); Class assertType = (Class) resolvableType.resolveGeneric(1); Class contextType = (Class) resolvableType.resolveGeneric(2); return ApplicationContextAssertProvider.get(assertType, contextType, this::createAndLoadContext); } private C createAndLoadContext() { C context = this.contextFactory.get(); try { configureContext(context); return context; } catch (RuntimeException ex) { context.close(); throw ex; } } private void configureContext(C context) { if (this.parent != null) { context.setParent(this.parent); } if (this.classLoader != null) { Assert.isInstanceOf(DefaultResourceLoader.class, context); ((DefaultResourceLoader) context).setClassLoader(this.classLoader); } this.environmentProperties.applyTo(context); Class[] classes = Configurations.getClasses(this.configurations); if (classes.length > 0) { ((AnnotationConfigRegistry) context).register(classes); } this.initializers.forEach((initializer) -> initializer.initialize(context)); context.refresh(); } private void accept(ContextConsumer consumer, A context) { try { consumer.accept(context); } catch (Throwable ex) { rethrow(ex); } } @SuppressWarnings("unchecked") private void rethrow(Throwable e) throws E { throw (E) e; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy