Polish ContextCustomizer support in the TCF

Issue: SPR-13998
This commit is contained in:
Sam Brannen 2016-03-11 20:52:10 +01:00
parent 87a3a5cb3b
commit cbd1342fbb
13 changed files with 165 additions and 185 deletions

View File

@ -16,34 +16,34 @@
package org.springframework.test.context; package org.springframework.test.context;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
/** /**
* Strategy interface for customizing {@link ApplicationContext application contexts} that * Strategy interface for customizing {@link ConfigurableApplicationContext
* are created and managed by the Spring TestContext Framework. * application contexts} that are created and managed by the <em>Spring
* TestContext Framework</em>.
* *
* <p>Customizers are loaded via {@link ContextCustomizerFactory} classes registered in * <p>Customizers are created by {@link ContextCustomizerFactory} implementations.
* {@code spring.factories}.
* *
* <p>Implementations should take care to implement correct {@code equals} and * <p>Implementations must implement correct {@code equals} and {@code hashCode}
* {@code hashCode} methods since customizers form part of the * methods since customizers form part of the {@link MergedContextConfiguration}
* {@link MergedContextConfiguration} which is used as a cache key. * which is used as a cache key.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Sam Brannen
* @since 4.3 * @since 4.3
* @see ContextCustomizerFactory * @see ContextCustomizerFactory
* @see org.springframework.test.context.support.AbstractContextLoader * @see org.springframework.test.context.support.AbstractContextLoader#customizeContext
*/ */
public interface ContextCustomizer { public interface ContextCustomizer {
/** /**
* Called <i>before</i> bean definitions are read to customize the * Customize the supplied {@code ConfigurableApplicationContext} <em>after</em>
* {@link ConfigurableApplicationContext}. * bean definitions have been loaded into the context but <em>before</em> the
* @param context the context that should be prepared * context has been refreshed.
* @param mergedContextConfiguration the merged context configuration * @param context the context to customize
* @param mergedConfig the merged context configuration
*/ */
void customizeContext(ConfigurableApplicationContext context, void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig);
MergedContextConfiguration mergedContextConfiguration);
} }

View File

@ -18,29 +18,35 @@ package org.springframework.test.context;
import java.util.List; import java.util.List;
import org.springframework.context.ConfigurableApplicationContext;
/** /**
* Factory registered in {@code spring.factories} that is used to create * Factory for creating {@link ContextCustomizer ContextCustomizers}.
* {@link ContextCustomizer ContextCustomizers}. Factories are called after *
* {@link ContextLoader ContextLoaders} have been triggered but before the * <p>Factories are invoked after {@link ContextLoader ContextLoaders} have
* processed context configuration attributes but before the
* {@link MergedContextConfiguration} is created. * {@link MergedContextConfiguration} is created.
* *
* <p>By default, the Spring TestContext Framework will use the
* {@link org.springframework.core.io.support.SpringFactoriesLoader SpringFactoriesLoader}
* mechanism for loading factories configured in all {@code META-INF/spring.factories}
* files on the classpath.
*
* @author Phillip Webb * @author Phillip Webb
* @author Sam Brannen
* @since 4.3 * @since 4.3
*/ */
public interface ContextCustomizerFactory { public interface ContextCustomizerFactory {
/** /**
* Get the {@link ContextCustomizer} (if any) that should be used to customize the * Create a {@link ContextCustomizer} that should be used to customize a
* {@link ConfigurableApplicationContext} when it is created. * {@link org.springframework.context.ConfigurableApplicationContext ConfigurableApplicationContext}
* before it is refreshed.
* @param testClass the test class * @param testClass the test class
* @param configurationAttributes he list of context configuration attributes for the * @param configAttributes the list of context configuration attributes for
* test class, ordered <em>bottom-up</em> (i.e., as if we were traversing up the class * the test class, ordered <em>bottom-up</em> (i.e., as if we were traversing
* hierarchy); never {@code null} or empty. * up the class hierarchy); never {@code null} or empty
* @return a {@link ContextCustomizer} or {@code null} * @return a {@link ContextCustomizer} or {@code null} if no customizer should
* be used
*/ */
ContextCustomizer getContextCustomizer(Class<?> testClass, ContextCustomizer createContextCustomizer(Class<?> testClass, List<ContextConfigurationAttributes> configAttributes);
List<ContextConfigurationAttributes> configurationAttributes);
} }

View File

@ -55,6 +55,7 @@ import org.springframework.util.StringUtils;
* that was loaded using properties of this {@code MergedContextConfiguration}. * that was loaded using properties of this {@code MergedContextConfiguration}.
* *
* @author Sam Brannen * @author Sam Brannen
* @author Phillip Webb
* @since 3.1 * @since 3.1
* @see ContextConfiguration * @see ContextConfiguration
* @see ContextHierarchy * @see ContextHierarchy
@ -74,6 +75,8 @@ public class MergedContextConfiguration implements Serializable {
private static final Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> EMPTY_INITIALIZER_CLASSES = private static final Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> EMPTY_INITIALIZER_CLASSES =
Collections.<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> emptySet(); Collections.<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> emptySet();
private static final Set<ContextCustomizer> EMPTY_CONTEXT_CUSTOMIZERS = Collections.<ContextCustomizer> emptySet();
private final Class<?> testClass; private final Class<?> testClass;
@ -113,6 +116,11 @@ public class MergedContextConfiguration implements Serializable {
Collections.unmodifiableSet(contextInitializerClasses) : EMPTY_INITIALIZER_CLASSES); Collections.unmodifiableSet(contextInitializerClasses) : EMPTY_INITIALIZER_CLASSES);
} }
private static Set<ContextCustomizer> processContextCustomizers(Set<ContextCustomizer> contextCustomizers) {
return (contextCustomizers != null ?
Collections.unmodifiableSet(contextCustomizers) : EMPTY_CONTEXT_CUSTOMIZERS);
}
private static String[] processActiveProfiles(String[] activeProfiles) { private static String[] processActiveProfiles(String[] activeProfiles) {
if (activeProfiles == null) { if (activeProfiles == null) {
return EMPTY_STRING_ARRAY; return EMPTY_STRING_ARRAY;
@ -247,8 +255,8 @@ public class MergedContextConfiguration implements Serializable {
* <p>If a {@code null} value is supplied for {@code locations}, * <p>If a {@code null} value is supplied for {@code locations},
* {@code classes}, {@code activeProfiles}, {@code propertySourceLocations}, * {@code classes}, {@code activeProfiles}, {@code propertySourceLocations},
* or {@code propertySourceProperties} an empty array will be stored instead. * or {@code propertySourceProperties} an empty array will be stored instead.
* If a {@code null} value is supplied for the * If a {@code null} value is supplied for {@code contextInitializerClasses}
* {@code contextInitializerClasses} an empty set will be stored instead. * or {@code contextCustomizers}, an empty set will be stored instead.
* Furthermore, active profiles will be sorted, and duplicate profiles * Furthermore, active profiles will be sorted, and duplicate profiles
* will be removed. * will be removed.
* @param testClass the test class for which the configuration was merged * @param testClass the test class for which the configuration was merged
@ -258,11 +266,12 @@ public class MergedContextConfiguration implements Serializable {
* @param activeProfiles the merged active bean definition profiles * @param activeProfiles the merged active bean definition profiles
* @param propertySourceLocations the merged {@code PropertySource} locations * @param propertySourceLocations the merged {@code PropertySource} locations
* @param propertySourceProperties the merged {@code PropertySource} properties * @param propertySourceProperties the merged {@code PropertySource} properties
* @param contextCustomizers the context customizers
* @param contextLoader the resolved {@code ContextLoader} * @param contextLoader the resolved {@code ContextLoader}
* @param cacheAwareContextLoaderDelegate a cache-aware context loader * @param cacheAwareContextLoaderDelegate a cache-aware context loader
* delegate with which to retrieve the parent context * delegate with which to retrieve the parent context
* @param parent the parent configuration or {@code null} if there is no parent * @param parent the parent configuration or {@code null} if there is no parent
* @since 4.2 * @since 4.3
*/ */
public MergedContextConfiguration(Class<?> testClass, String[] locations, Class<?>[] classes, public MergedContextConfiguration(Class<?> testClass, String[] locations, Class<?>[] classes,
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses, Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses,
@ -277,7 +286,7 @@ public class MergedContextConfiguration implements Serializable {
this.activeProfiles = processActiveProfiles(activeProfiles); this.activeProfiles = processActiveProfiles(activeProfiles);
this.propertySourceLocations = processStrings(propertySourceLocations); this.propertySourceLocations = processStrings(propertySourceLocations);
this.propertySourceProperties = processStrings(propertySourceProperties); this.propertySourceProperties = processStrings(propertySourceProperties);
this.contextCustomizers = Collections.unmodifiableSet(contextCustomizers); this.contextCustomizers = processContextCustomizers(contextCustomizers);
this.contextLoader = contextLoader; this.contextLoader = contextLoader;
this.cacheAwareContextLoaderDelegate = cacheAwareContextLoaderDelegate; this.cacheAwareContextLoaderDelegate = cacheAwareContextLoaderDelegate;
this.parent = parent; this.parent = parent;
@ -390,7 +399,7 @@ public class MergedContextConfiguration implements Serializable {
* when the application context is loaded. * when the application context is loaded.
*/ */
public Set<ContextCustomizer> getContextCustomizers() { public Set<ContextCustomizer> getContextCustomizers() {
return contextCustomizers; return this.contextCustomizers;
} }
/** /**
@ -515,6 +524,7 @@ public class MergedContextConfiguration implements Serializable {
* {@linkplain #getActiveProfiles() active profiles}, * {@linkplain #getActiveProfiles() active profiles},
* {@linkplain #getPropertySourceLocations() property source locations}, * {@linkplain #getPropertySourceLocations() property source locations},
* {@linkplain #getPropertySourceProperties() property source properties}, * {@linkplain #getPropertySourceProperties() property source properties},
* {@linkplain #getContextCustomizers() context customizers},
* the name of the {@link #getContextLoader() ContextLoader}, and the * the name of the {@link #getContextLoader() ContextLoader}, and the
* {@linkplain #getParent() parent configuration}. * {@linkplain #getParent() parent configuration}.
*/ */
@ -528,6 +538,7 @@ public class MergedContextConfiguration implements Serializable {
.append("activeProfiles", ObjectUtils.nullSafeToString(this.activeProfiles)) .append("activeProfiles", ObjectUtils.nullSafeToString(this.activeProfiles))
.append("propertySourceLocations", ObjectUtils.nullSafeToString(this.propertySourceLocations)) .append("propertySourceLocations", ObjectUtils.nullSafeToString(this.propertySourceLocations))
.append("propertySourceProperties", ObjectUtils.nullSafeToString(this.propertySourceProperties)) .append("propertySourceProperties", ObjectUtils.nullSafeToString(this.propertySourceProperties))
.append("contextCustomizers", this.contextCustomizers)
.append("contextLoader", nullSafeToString(this.contextLoader)) .append("contextLoader", nullSafeToString(this.contextLoader))
.append("parent", this.parent) .append("parent", this.parent)
.toString(); .toString();

View File

@ -28,7 +28,6 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException; import org.springframework.context.ApplicationContextException;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.GenericTypeResolver; import org.springframework.core.GenericTypeResolver;
import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
@ -58,10 +57,13 @@ import org.springframework.util.ResourceUtils;
* *
* @author Sam Brannen * @author Sam Brannen
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Phillip Webb
* @since 2.5 * @since 2.5
* @see #generateDefaultLocations * @see #generateDefaultLocations
* @see #getResourceSuffixes * @see #getResourceSuffixes
* @see #modifyLocations * @see #modifyLocations
* @see #prepareContext
* @see #customizeContext
*/ */
public abstract class AbstractContextLoader implements SmartContextLoader { public abstract class AbstractContextLoader implements SmartContextLoader {
@ -109,8 +111,6 @@ public abstract class AbstractContextLoader implements SmartContextLoader {
* {@linkplain MergedContextConfiguration#getPropertySourceProperties() * {@linkplain MergedContextConfiguration#getPropertySourceProperties()
* inlined properties} from the supplied {@code MergedContextConfiguration} * inlined properties} from the supplied {@code MergedContextConfiguration}
* to the {@code Environment} of the context.</li> * to the {@code Environment} of the context.</li>
* <li>Calls any {@link MergedContextConfiguration#getContextCustomizers()
* ContextCustomizers} that are part of the {@link MergedContextConfiguration}.</li>
* <li>Determines what (if any) context initializer classes have been supplied * <li>Determines what (if any) context initializer classes have been supplied
* via the {@code MergedContextConfiguration} and instantiates and * via the {@code MergedContextConfiguration} and instantiates and
* {@linkplain ApplicationContextInitializer#initialize invokes} each with the * {@linkplain ApplicationContextInitializer#initialize invokes} each with the
@ -173,18 +173,16 @@ public abstract class AbstractContextLoader implements SmartContextLoader {
/** /**
* Customize the {@link ConfigurableApplicationContext} created by this * Customize the {@link ConfigurableApplicationContext} created by this
* {@code ContextLoader} <i>after</i> bean definitions have been * {@code ContextLoader} <em>after</em> bean definitions have been loaded
* loaded into the context but <i>before</i> the context is refreshed. * into the context but <em>before</em> the context has been refreshed.
* * <p>The default implementation delegates to all
* <p>The default implementation triggers all the * {@link MergedContextConfiguration#getContextCustomizers context customizers}
* {@link MergedContextConfiguration#getContextCustomizers() context customizers} that * that have been registered with the supplied {@code mergedConfig}.
* have been registered with the {@code mergedConfig}.
*
* @param context the newly created application context * @param context the newly created application context
* @param mergedConfig the merged context configuration * @param mergedConfig the merged context configuration
* @since 4.3 * @since 4.3
*/ */
protected void customizeContext(GenericApplicationContext context, MergedContextConfiguration mergedConfig) { protected void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
for (ContextCustomizer contextCustomizer : mergedConfig.getContextCustomizers()) { for (ContextCustomizer contextCustomizer : mergedConfig.getContextCustomizers()) {
contextCustomizer.customizeContext(context, mergedConfig); contextCustomizer.customizeContext(context, mergedConfig);
} }

View File

@ -253,7 +253,8 @@ public abstract class AbstractDelegatingSmartContextLoader implements SmartConte
} }
// If neither of the candidates supports the mergedConfig based on resources but // If neither of the candidates supports the mergedConfig based on resources but
// ACIs were declared, then delegate to the annotation config loader. // ACIs or customizers were declared, then delegate to the annotation config
// loader.
if (!mergedConfig.getContextInitializerClasses().isEmpty() || !mergedConfig.getContextCustomizers().isEmpty()) { if (!mergedConfig.getContextInitializerClasses().isEmpty() || !mergedConfig.getContextCustomizers().isEmpty()) {
return delegateLoading(getAnnotationConfigLoader(), mergedConfig); return delegateLoading(getAnnotationConfigLoader(), mergedConfig);
} }

View File

@ -16,7 +16,6 @@
package org.springframework.test.context.support; package org.springframework.test.context.support;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
@ -53,6 +52,7 @@ import org.springframework.util.StringUtils;
* *
* @author Sam Brannen * @author Sam Brannen
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Phillip Webb
* @since 2.5 * @since 2.5
* @see #loadContext(MergedContextConfiguration) * @see #loadContext(MergedContextConfiguration)
* @see #loadContext(String...) * @see #loadContext(String...)
@ -92,6 +92,8 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
* annotation configuration processors.</li> * annotation configuration processors.</li>
* <li>Calls {@link #customizeContext(GenericApplicationContext)} to allow for customizing the context * <li>Calls {@link #customizeContext(GenericApplicationContext)} to allow for customizing the context
* before it is refreshed.</li> * before it is refreshed.</li>
* <li>Calls {@link #customizeContext(ConfigurableApplicationContext, MergedContextConfiguration)} to
* allow for customizing the context before it is refreshed.</li>
* <li>{@link ConfigurableApplicationContext#refresh Refreshes} the * <li>{@link ConfigurableApplicationContext#refresh Refreshes} the
* context and registers a JVM shutdown hook for it.</li> * context and registers a JVM shutdown hook for it.</li>
* </ul> * </ul>
@ -206,6 +208,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
* @see GenericApplicationContext#setAllowBeanDefinitionOverriding * @see GenericApplicationContext#setAllowBeanDefinitionOverriding
* @see GenericApplicationContext#setResourceLoader * @see GenericApplicationContext#setResourceLoader
* @see GenericApplicationContext#setId * @see GenericApplicationContext#setId
* @see #prepareContext(ConfigurableApplicationContext, MergedContextConfiguration)
* @since 2.5 * @since 2.5
*/ */
protected void prepareContext(GenericApplicationContext context) { protected void prepareContext(GenericApplicationContext context) {
@ -279,6 +282,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
* @param context the newly created application context * @param context the newly created application context
* @see #loadContext(MergedContextConfiguration) * @see #loadContext(MergedContextConfiguration)
* @see #loadContext(String...) * @see #loadContext(String...)
* @see #customizeContext(ConfigurableApplicationContext, MergedContextConfiguration)
* @since 2.5 * @since 2.5
*/ */
protected void customizeContext(GenericApplicationContext context) { protected void customizeContext(GenericApplicationContext context) {

View File

@ -410,12 +410,16 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
return processMergedContextConfiguration(mergedConfig); return processMergedContextConfiguration(mergedConfig);
} }
/**
* @since 4.3
*/
private Set<ContextCustomizer> getContextCustomizers(Class<?> testClass, private Set<ContextCustomizer> getContextCustomizers(Class<?> testClass,
List<ContextConfigurationAttributes> configurationAttributes) { List<ContextConfigurationAttributes> configAttributes) {
List<ContextCustomizerFactory> factories = geContextCustomizerFactories();
List<ContextCustomizerFactory> factories = getContextCustomizerFactories();
Set<ContextCustomizer> customizers = new LinkedHashSet<ContextCustomizer>(factories.size()); Set<ContextCustomizer> customizers = new LinkedHashSet<ContextCustomizer>(factories.size());
for (ContextCustomizerFactory factory : factories) { for (ContextCustomizerFactory factory : factories) {
ContextCustomizer customizer = factory.getContextCustomizer(testClass, configurationAttributes); ContextCustomizer customizer = factory.createContextCustomizer(testClass, configAttributes);
if (customizer != null) { if (customizer != null) {
customizers.add(customizer); customizers.add(customizer);
} }
@ -424,13 +428,20 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
} }
/** /**
* Get the default {@link ContextCustomizerFactory} instances for this bootstrapper. * Get the {@link ContextCustomizerFactory} instances for this bootstrapper.
* <p>The default implementation uses the {@link SpringFactoriesLoader} mechanism
* for loading factories configured in all {@code META-INF/spring.factories}
* files on the classpath.
* @since 4.3
* @see SpringFactoriesLoader#loadFactories
*/ */
protected List<ContextCustomizerFactory> geContextCustomizerFactories() { protected List<ContextCustomizerFactory> getContextCustomizerFactories() {
return SpringFactoriesLoader.loadFactories(ContextCustomizerFactory.class, return SpringFactoriesLoader.loadFactories(ContextCustomizerFactory.class, getClass().getClassLoader());
getClass().getClassLoader());
} }
/**
* @since 4.3
*/
private boolean areAllEmpty(Collection<?>... collections) { private boolean areAllEmpty(Collection<?>... collections) {
for (Collection<?> collection : collections) { for (Collection<?> collection : collections) {
if (!collection.isEmpty()) { if (!collection.isEmpty()) {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 the original author or authors. * Copyright 2002-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -53,6 +53,7 @@ import org.springframework.web.context.support.GenericWebApplicationContext;
* {@link #loadBeanDefinitions}. * {@link #loadBeanDefinitions}.
* *
* @author Sam Brannen * @author Sam Brannen
* @author Phillip Webb
* @since 3.2 * @since 3.2
* @see #loadContext(MergedContextConfiguration) * @see #loadContext(MergedContextConfiguration)
* @see #loadContext(String...) * @see #loadContext(String...)
@ -256,10 +257,14 @@ public abstract class AbstractGenericWebContextLoader extends AbstractContextLoa
* loader <i>after</i> bean definitions have been loaded into the context but * loader <i>after</i> bean definitions have been loaded into the context but
* <i>before</i> the context is refreshed. * <i>before</i> the context is refreshed.
* *
* <p>The default implementation simply delegates to
* {@link AbstractContextLoader#customizeContext(ConfigurableApplicationContext, MergedContextConfiguration)}.
*
* @param context the newly created web application context * @param context the newly created web application context
* @param webMergedConfig the merged context configuration to use to load the * @param webMergedConfig the merged context configuration to use to load the
* web application context * web application context
* @see #loadContext(MergedContextConfiguration) * @see #loadContext(MergedContextConfiguration)
* @see #customizeContext(ConfigurableApplicationContext, MergedContextConfiguration)
*/ */
protected void customizeContext(GenericWebApplicationContext context, WebMergedContextConfiguration webMergedConfig) { protected void customizeContext(GenericWebApplicationContext context, WebMergedContextConfiguration webMergedConfig) {
super.customizeContext(context, webMergedConfig); super.customizeContext(context, webMergedConfig);

View File

@ -22,6 +22,7 @@ import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.style.ToStringCreator; import org.springframework.core.style.ToStringCreator;
import org.springframework.test.context.CacheAwareContextLoaderDelegate; import org.springframework.test.context.CacheAwareContextLoaderDelegate;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextLoader; import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration; import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
@ -132,12 +133,47 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
String resourceBasePath, ContextLoader contextLoader, String resourceBasePath, ContextLoader contextLoader,
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) { CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) {
super(testClass, locations, classes, contextInitializerClasses, activeProfiles, propertySourceLocations, this(testClass, locations, classes, contextInitializerClasses, activeProfiles, propertySourceLocations,
propertySourceProperties, contextLoader, cacheAwareContextLoaderDelegate, parent); propertySourceProperties, null, resourceBasePath, contextLoader, cacheAwareContextLoaderDelegate, parent);
this.resourceBasePath = !StringUtils.hasText(resourceBasePath) ? "" : resourceBasePath;
} }
/**
* Create a new {@code WebMergedContextConfiguration} instance for the
* supplied parameters.
* <p>If a {@code null} value is supplied for {@code locations},
* {@code classes}, {@code activeProfiles}, {@code propertySourceLocations},
* or {@code propertySourceProperties} an empty array will be stored instead.
* If a {@code null} value is supplied for {@code contextInitializerClasses}
* or {@code contextCustomizers}, an empty set will be stored instead.
* If an <em>empty</em> value is supplied for the {@code resourceBasePath}
* an empty string will be used. Furthermore, active profiles will be sorted,
* and duplicate profiles will be removed.
* @param testClass the test class for which the configuration was merged
* @param locations the merged context resource locations
* @param classes the merged annotated classes
* @param contextInitializerClasses the merged context initializer classes
* @param activeProfiles the merged active bean definition profiles
* @param propertySourceLocations the merged {@code PropertySource} locations
* @param propertySourceProperties the merged {@code PropertySource} properties
* @param contextCustomizers the context customizers
* @param resourceBasePath the resource path to the root directory of the web application
* @param contextLoader the resolved {@code ContextLoader}
* @param cacheAwareContextLoaderDelegate a cache-aware context loader
* delegate with which to retrieve the parent context
* @param parent the parent configuration or {@code null} if there is no parent
* @since 4.3
*/
public WebMergedContextConfiguration(Class<?> testClass, String[] locations, Class<?>[] classes,
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses,
String[] activeProfiles, String[] propertySourceLocations, String[] propertySourceProperties,
Set<ContextCustomizer> contextCustomizers, String resourceBasePath, ContextLoader contextLoader,
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) {
super(testClass, locations, classes, contextInitializerClasses, activeProfiles, propertySourceLocations,
propertySourceProperties, contextCustomizers, contextLoader, cacheAwareContextLoaderDelegate, parent);
this.resourceBasePath = (StringUtils.hasText(resourceBasePath) ? resourceBasePath : "");
}
/** /**
* Get the resource path to the root directory of the web application for the * Get the resource path to the root directory of the web application for the
@ -182,6 +218,7 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
* {@linkplain #getActiveProfiles() active profiles}, * {@linkplain #getActiveProfiles() active profiles},
* {@linkplain #getPropertySourceLocations() property source locations}, * {@linkplain #getPropertySourceLocations() property source locations},
* {@linkplain #getPropertySourceProperties() property source properties}, * {@linkplain #getPropertySourceProperties() property source properties},
* {@linkplain #getContextCustomizers() context customizers},
* {@linkplain #getResourceBasePath() resource base path}, the name of the * {@linkplain #getResourceBasePath() resource base path}, the name of the
* {@link #getContextLoader() ContextLoader}, and the * {@link #getContextLoader() ContextLoader}, and the
* {@linkplain #getParent() parent configuration}. * {@linkplain #getParent() parent configuration}.
@ -196,6 +233,7 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
.append("activeProfiles", ObjectUtils.nullSafeToString(getActiveProfiles())) .append("activeProfiles", ObjectUtils.nullSafeToString(getActiveProfiles()))
.append("propertySourceLocations", ObjectUtils.nullSafeToString(getPropertySourceLocations())) .append("propertySourceLocations", ObjectUtils.nullSafeToString(getPropertySourceLocations()))
.append("propertySourceProperties", ObjectUtils.nullSafeToString(getPropertySourceProperties())) .append("propertySourceProperties", ObjectUtils.nullSafeToString(getPropertySourceProperties()))
.append("contextCustomizers", getContextCustomizers())
.append("resourceBasePath", getResourceBasePath()) .append("resourceBasePath", getResourceBasePath())
.append("contextLoader", nullSafeToString(getContextLoader())) .append("contextLoader", nullSafeToString(getContextLoader()))
.append("parent", getParent()) .append("parent", getParent())

View File

@ -39,6 +39,7 @@ import static org.mockito.Mockito.*;
* {@link org.springframework.test.context.cache.ContextCache ContextCache}. * {@link org.springframework.test.context.cache.ContextCache ContextCache}.
* *
* @author Sam Brannen * @author Sam Brannen
* @author Phillip Webb
* @since 3.1 * @since 3.1
*/ */
public class MergedContextConfigurationTests { public class MergedContextConfigurationTests {
@ -402,32 +403,31 @@ public class MergedContextConfigurationTests {
assertNotEquals(mergedConfig2, mergedConfig1); assertNotEquals(mergedConfig2, mergedConfig1);
} }
/**
* @since 4.3
*/
@Test @Test
public void equalsWithSameContextCustomizers() { public void equalsWithSameContextCustomizers() {
Set<ContextCustomizer> customizers1 = Collections.singleton( Set<ContextCustomizer> customizers = Collections.singleton(mock(ContextCustomizer.class));
mock(ContextCustomizer.class)); MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration( EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, null, null, customizers, loader, null, null);
getClass(), EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, null, MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_STRING_ARRAY, null, null, customizers1, loader, null, null); EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, null, null, customizers, loader, null, null);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(
getClass(), EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, null,
EMPTY_STRING_ARRAY, null, null, customizers1, loader, null, null);
assertEquals(mergedConfig1, mergedConfig2); assertEquals(mergedConfig1, mergedConfig2);
} }
/**
* @since 4.3
*/
@Test @Test
public void equalsWithDifferentContextCustomizers() { public void equalsWithDifferentContextCustomizers() {
Set<ContextCustomizer> customizers1 = Collections.singleton( Set<ContextCustomizer> customizers1 = Collections.singleton(mock(ContextCustomizer.class));
mock(ContextCustomizer.class)); Set<ContextCustomizer> customizers2 = Collections.singleton(mock(ContextCustomizer.class));
Set<ContextCustomizer> customizers2 = Collections.singleton(
mock(ContextCustomizer.class));
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration( MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
getClass(), EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, null, EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, null, null, customizers1, loader, null, null);
EMPTY_STRING_ARRAY, null, null, customizers1, loader, null, null); MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration( EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, null, null, customizers2, loader, null, null);
getClass(), EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, null,
EMPTY_STRING_ARRAY, null, null, customizers2, loader, null, null);
assertNotEquals(mergedConfig1, mergedConfig2); assertNotEquals(mergedConfig1, mergedConfig2);
assertNotEquals(mergedConfig2, mergedConfig1); assertNotEquals(mergedConfig2, mergedConfig1);
} }

View File

@ -1,71 +0,0 @@
/*
* Copyright 2002-2016 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.test.context;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import static org.hamcrest.CoreMatchers.*;
/**
* JUnit 4 based unit test for {@link TestContextManager}, which verifies
* ContextConfiguration attributes are defined.
*
* @author Phillip Webb
* @since 4.3
*/
public class TestContextManagerVerifyAttributesTests {
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Test
public void processContextConfigurationWithMissingContextConfigAttributes() {
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage(containsString("was unable to detect defaults, "
+ "and no ApplicationContextInitializers or ContextCustomizers were "
+ "declared for context configuration"));
new TestContextManager(MissingContextAttributes.class);
}
@Test
public void processContextConfigurationWitListener() {
new TestContextManager(WithInitializer.class);
}
@ContextConfiguration
private static class MissingContextAttributes {
}
@ContextConfiguration(initializers=ExampleInitializer.class)
private static class WithInitializer {
}
static class ExampleInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
}
}
}

View File

@ -16,73 +16,50 @@
package org.springframework.test.context.junit4; package org.springframework.test.context.junit4;
import java.util.Collections;
import java.util.List; import java.util.List;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.BootstrapWith; import org.springframework.test.context.BootstrapWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer; import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory; import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.junit4.ContextCustomizerSpringRunnerTests.CustomTestContextBootstrapper; import org.springframework.test.context.junit4.ContextCustomizerSpringRunnerTests.CustomTestContextBootstrapper;
import org.springframework.test.context.support.DefaultTestContextBootstrapper; import org.springframework.test.context.support.DefaultTestContextBootstrapper;
import static java.util.Collections.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
/** /**
* JUnit 4 based integration test which verifies support of * JUnit 4 based integration test which verifies support of
* {@link ContextCustomizerFactory} and {@link ContextCustomizer}. * {@link ContextCustomizerFactory} and {@link ContextCustomizer}.
* *
* @author Sam Brannen
* @author Phillip Webb * @author Phillip Webb
* @since 4.3 * @since 4.3
*/ */
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringRunner.class)
@BootstrapWith(CustomTestContextBootstrapper.class) @BootstrapWith(CustomTestContextBootstrapper.class)
@ContextConfiguration
public class ContextCustomizerSpringRunnerTests { public class ContextCustomizerSpringRunnerTests {
@Autowired @Autowired String foo;
private MyBean myBean;
@Test @Test
public void injectedMyBean() throws Exception { public void injectedBean() {
assertNotNull(this.myBean); assertEquals("foo", foo);
} }
public static class CustomTestContextBootstrapper
extends DefaultTestContextBootstrapper { static class CustomTestContextBootstrapper extends DefaultTestContextBootstrapper {
@Override @Override
protected List<ContextCustomizerFactory> geContextCustomizerFactories() { protected List<ContextCustomizerFactory> getContextCustomizerFactories() {
return Collections.singletonList(new ContextCustomizerFactory() { return singletonList((testClass, configAttributes) ->
// ContextCustomizer as lambda expression:
@Override (context, mergedConfig) -> context.getBeanFactory().registerSingleton("foo", "foo"));
public ContextCustomizer getContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configurationAttributes) {
return new TestContextCustomizers();
}
});
} }
}
public static class TestContextCustomizers implements ContextCustomizer {
@Override
public void customizeContext(ConfigurableApplicationContext context,
MergedContextConfiguration mergedContextConfiguration) {
context.getBeanFactory().registerSingleton("mybean", new MyBean());
}
}
public static class MyBean {
} }
} }

View File

@ -63,7 +63,7 @@ public class BootstrapTestUtilsMergedConfigTests extends AbstractContextConfigur
public void buildMergedConfigWithContextConfigurationWithoutLocationsClassesOrInitializers() { public void buildMergedConfigWithContextConfigurationWithoutLocationsClassesOrInitializers() {
exception.expect(IllegalStateException.class); exception.expect(IllegalStateException.class);
exception.expectMessage(startsWith("DelegatingSmartContextLoader was unable to detect defaults, " exception.expectMessage(startsWith("DelegatingSmartContextLoader was unable to detect defaults, "
+ "and no ApplicationContextInitializers or ContextCustomizers were declared for context configuration attribute")); + "and no ApplicationContextInitializers or ContextCustomizers were declared for context configuration attributes"));
buildMergedContextConfiguration(MissingContextAttributesTestCase.class); buildMergedContextConfiguration(MissingContextAttributesTestCase.class);
} }