diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextCustomizer.java b/spring-test/src/main/java/org/springframework/test/context/ContextCustomizer.java new file mode 100644 index 0000000000..d9462faadb --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/ContextCustomizer.java @@ -0,0 +1,49 @@ +/* + * 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.springframework.context.ConfigurableApplicationContext; + +/** + * Strategy interface for customizing {@link ConfigurableApplicationContext + * application contexts} that are created and managed by the Spring + * TestContext Framework. + * + *

Customizers are created by {@link ContextCustomizerFactory} implementations. + * + *

Implementations must implement correct {@code equals} and {@code hashCode} + * methods since customizers form part of the {@link MergedContextConfiguration} + * which is used as a cache key. + * + * @author Phillip Webb + * @author Sam Brannen + * @since 4.3 + * @see ContextCustomizerFactory + * @see org.springframework.test.context.support.AbstractContextLoader#customizeContext + */ +public interface ContextCustomizer { + + /** + * Customize the supplied {@code ConfigurableApplicationContext} after + * bean definitions have been loaded into the context but before the + * context has been refreshed. + * @param context the context to customize + * @param mergedConfig the merged context configuration + */ + void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig); + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextCustomizerFactory.java b/spring-test/src/main/java/org/springframework/test/context/ContextCustomizerFactory.java new file mode 100644 index 0000000000..9f07700a38 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/ContextCustomizerFactory.java @@ -0,0 +1,52 @@ +/* + * 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 java.util.List; + +/** + * Factory for creating {@link ContextCustomizer ContextCustomizers}. + * + *

Factories are invoked after {@link ContextLoader ContextLoaders} have + * processed context configuration attributes but before the + * {@link MergedContextConfiguration} is created. + * + *

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 Sam Brannen + * @since 4.3 + */ +public interface ContextCustomizerFactory { + + /** + * Create a {@link ContextCustomizer} that should be used to customize a + * {@link org.springframework.context.ConfigurableApplicationContext ConfigurableApplicationContext} + * before it is refreshed. + * @param testClass the test class + * @param configAttributes the list of context configuration attributes for + * the test class, ordered bottom-up (i.e., as if we were traversing + * up the class hierarchy); never {@code null} or empty + * @return a {@link ContextCustomizer} or {@code null} if no customizer should + * be used + */ + ContextCustomizer createContextCustomizer(Class testClass, List configAttributes); + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java index 5090d7c5a4..dc98c1c276 100644 --- a/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java @@ -55,6 +55,7 @@ import org.springframework.util.StringUtils; * that was loaded using properties of this {@code MergedContextConfiguration}. * * @author Sam Brannen + * @author Phillip Webb * @since 3.1 * @see ContextConfiguration * @see ContextHierarchy @@ -74,6 +75,8 @@ public class MergedContextConfiguration implements Serializable { private static final Set>> EMPTY_INITIALIZER_CLASSES = Collections.>> emptySet(); + private static final Set EMPTY_CONTEXT_CUSTOMIZERS = Collections. emptySet(); + private final Class testClass; @@ -89,6 +92,8 @@ public class MergedContextConfiguration implements Serializable { private final String[] propertySourceProperties; + private final Set contextCustomizers; + private final ContextLoader contextLoader; private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate; @@ -111,6 +116,11 @@ public class MergedContextConfiguration implements Serializable { Collections.unmodifiableSet(contextInitializerClasses) : EMPTY_INITIALIZER_CLASSES); } + private static Set processContextCustomizers(Set contextCustomizers) { + return (contextCustomizers != null ? + Collections.unmodifiableSet(contextCustomizers) : EMPTY_CONTEXT_CUSTOMIZERS); + } + private static String[] processActiveProfiles(String[] activeProfiles) { if (activeProfiles == null) { return EMPTY_STRING_ARRAY; @@ -201,8 +211,8 @@ public class MergedContextConfiguration implements Serializable { public MergedContextConfiguration(MergedContextConfiguration mergedConfig) { this(mergedConfig.testClass, mergedConfig.locations, mergedConfig.classes, mergedConfig.contextInitializerClasses, mergedConfig.activeProfiles, mergedConfig.propertySourceLocations, - mergedConfig.propertySourceProperties, mergedConfig.contextLoader, - mergedConfig.cacheAwareContextLoaderDelegate, mergedConfig.parent); + mergedConfig.propertySourceProperties, mergedConfig.contextCustomizers, + mergedConfig.contextLoader, mergedConfig.cacheAwareContextLoaderDelegate, mergedConfig.parent); } /** @@ -233,6 +243,41 @@ public class MergedContextConfiguration implements Serializable { String[] activeProfiles, String[] propertySourceLocations, String[] propertySourceProperties, ContextLoader contextLoader, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) { + this(testClass, locations, classes, contextInitializerClasses, activeProfiles, + propertySourceLocations, propertySourceProperties, + Collections. emptySet(), contextLoader, + cacheAwareContextLoaderDelegate, parent); + } + + /** + * Create a new {@code MergedContextConfiguration} instance for the + * supplied parameters. + *

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. + * 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 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 MergedContextConfiguration(Class testClass, String[] locations, Class[] classes, + Set>> contextInitializerClasses, + String[] activeProfiles, String[] propertySourceLocations, String[] propertySourceProperties, + Set contextCustomizers, ContextLoader contextLoader, + CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) { this.testClass = testClass; this.locations = processStrings(locations); @@ -241,6 +286,7 @@ public class MergedContextConfiguration implements Serializable { this.activeProfiles = processActiveProfiles(activeProfiles); this.propertySourceLocations = processStrings(propertySourceLocations); this.propertySourceProperties = processStrings(propertySourceProperties); + this.contextCustomizers = processContextCustomizers(contextCustomizers); this.contextLoader = contextLoader; this.cacheAwareContextLoaderDelegate = cacheAwareContextLoaderDelegate; this.parent = parent; @@ -348,6 +394,14 @@ public class MergedContextConfiguration implements Serializable { return this.propertySourceProperties; } + /** + * Get the merged {@link ContextCustomizer ContextCustomizers} that will be applied + * when the application context is loaded. + */ + public Set getContextCustomizers() { + return this.contextCustomizers; + } + /** * Get the resolved {@link ContextLoader} for the {@linkplain #getTestClass() test class}. */ @@ -424,6 +478,9 @@ public class MergedContextConfiguration implements Serializable { if (!Arrays.equals(this.propertySourceProperties, otherConfig.propertySourceProperties)) { return false; } + if (!this.contextCustomizers.equals(otherConfig.contextCustomizers)) { + return false; + } if (this.parent == null) { if (otherConfig.parent != null) { @@ -454,6 +511,7 @@ public class MergedContextConfiguration implements Serializable { result = 31 * result + Arrays.hashCode(this.activeProfiles); result = 31 * result + Arrays.hashCode(this.propertySourceLocations); result = 31 * result + Arrays.hashCode(this.propertySourceProperties); + result = 31 * result + this.contextCustomizers.hashCode(); result = 31 * result + (this.parent != null ? this.parent.hashCode() : 0); result = 31 * result + nullSafeToString(this.contextLoader).hashCode(); return result; @@ -466,6 +524,7 @@ public class MergedContextConfiguration implements Serializable { * {@linkplain #getActiveProfiles() active profiles}, * {@linkplain #getPropertySourceLocations() property source locations}, * {@linkplain #getPropertySourceProperties() property source properties}, + * {@linkplain #getContextCustomizers() context customizers}, * the name of the {@link #getContextLoader() ContextLoader}, and the * {@linkplain #getParent() parent configuration}. */ @@ -479,6 +538,7 @@ public class MergedContextConfiguration implements Serializable { .append("activeProfiles", ObjectUtils.nullSafeToString(this.activeProfiles)) .append("propertySourceLocations", ObjectUtils.nullSafeToString(this.propertySourceLocations)) .append("propertySourceProperties", ObjectUtils.nullSafeToString(this.propertySourceProperties)) + .append("contextCustomizers", this.contextCustomizers) .append("contextLoader", nullSafeToString(this.contextLoader)) .append("parent", this.parent) .toString(); diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java index c1198d0b10..f17782f8a6 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java @@ -33,6 +33,7 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.env.PropertySource; import org.springframework.core.io.ClassPathResource; import org.springframework.test.context.ContextConfigurationAttributes; +import org.springframework.test.context.ContextCustomizer; import org.springframework.test.context.ContextLoader; import org.springframework.test.context.MergedContextConfiguration; import org.springframework.test.context.SmartContextLoader; @@ -56,10 +57,13 @@ import org.springframework.util.ResourceUtils; * * @author Sam Brannen * @author Juergen Hoeller + * @author Phillip Webb * @since 2.5 * @see #generateDefaultLocations * @see #getResourceSuffixes * @see #modifyLocations + * @see #prepareContext + * @see #customizeContext */ public abstract class AbstractContextLoader implements SmartContextLoader { @@ -167,6 +171,23 @@ public abstract class AbstractContextLoader implements SmartContextLoader { } } + /** + * Customize the {@link ConfigurableApplicationContext} created by this + * {@code ContextLoader} after bean definitions have been loaded + * into the context but before the context has been refreshed. + *

The default implementation delegates to all + * {@link MergedContextConfiguration#getContextCustomizers context customizers} + * that have been registered with the supplied {@code mergedConfig}. + * @param context the newly created application context + * @param mergedConfig the merged context configuration + * @since 4.3 + */ + protected void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) { + for (ContextCustomizer contextCustomizer : mergedConfig.getContextCustomizers()) { + contextCustomizer.customizeContext(context, mergedConfig); + } + } + // --- ContextLoader ------------------------------------------------------- diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java index 8f942194f9..c0ae608ed7 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java @@ -253,8 +253,9 @@ public abstract class AbstractDelegatingSmartContextLoader implements SmartConte } // If neither of the candidates supports the mergedConfig based on resources but - // ACIs were declared, then delegate to the annotation config loader. - if (!mergedConfig.getContextInitializerClasses().isEmpty()) { + // ACIs or customizers were declared, then delegate to the annotation config + // loader. + if (!mergedConfig.getContextInitializerClasses().isEmpty() || !mergedConfig.getContextCustomizers().isEmpty()) { return delegateLoading(getAnnotationConfigLoader(), mergedConfig); } diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java index 41e42fcf4e..4bc65d2941 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java @@ -16,7 +16,6 @@ package org.springframework.test.context.support; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -53,6 +52,7 @@ import org.springframework.util.StringUtils; * * @author Sam Brannen * @author Juergen Hoeller + * @author Phillip Webb * @since 2.5 * @see #loadContext(MergedContextConfiguration) * @see #loadContext(String...) @@ -92,6 +92,8 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader * annotation configuration processors. *

  • Calls {@link #customizeContext(GenericApplicationContext)} to allow for customizing the context * before it is refreshed.
  • + *
  • Calls {@link #customizeContext(ConfigurableApplicationContext, MergedContextConfiguration)} to + * allow for customizing the context before it is refreshed.
  • *
  • {@link ConfigurableApplicationContext#refresh Refreshes} the * context and registers a JVM shutdown hook for it.
  • * @@ -122,6 +124,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader loadBeanDefinitions(context, mergedConfig); AnnotationConfigUtils.registerAnnotationConfigProcessors(context); customizeContext(context); + customizeContext(context, mergedConfig); context.refresh(); context.registerShutdownHook(); return context; @@ -205,6 +208,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader * @see GenericApplicationContext#setAllowBeanDefinitionOverriding * @see GenericApplicationContext#setResourceLoader * @see GenericApplicationContext#setId + * @see #prepareContext(ConfigurableApplicationContext, MergedContextConfiguration) * @since 2.5 */ protected void prepareContext(GenericApplicationContext context) { @@ -278,6 +282,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader * @param context the newly created application context * @see #loadContext(MergedContextConfiguration) * @see #loadContext(String...) + * @see #customizeContext(ConfigurableApplicationContext, MergedContextConfiguration) * @since 2.5 */ protected void customizeContext(GenericApplicationContext context) { diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java index ab87d056f7..51bbee68da 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java @@ -38,6 +38,8 @@ import org.springframework.test.context.BootstrapContext; import org.springframework.test.context.CacheAwareContextLoaderDelegate; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfigurationAttributes; +import org.springframework.test.context.ContextCustomizer; +import org.springframework.test.context.ContextCustomizerFactory; import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.ContextLoader; import org.springframework.test.context.MergedContextConfiguration; @@ -385,10 +387,13 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot } } - if (requireLocationsClassesOrInitializers && areAllEmpty(locations, classes, initializers)) { + Set contextCustomizers = getContextCustomizers(testClass, + Collections.unmodifiableList(configAttributesList)); + + if (requireLocationsClassesOrInitializers && areAllEmpty(locations, classes, initializers, contextCustomizers)) { throw new IllegalStateException(String.format( "%s was unable to detect defaults, and no ApplicationContextInitializers " - + "were declared for context configuration attributes %s", + + "or ContextCustomizers were declared for context configuration attributes %s", contextLoader.getClass().getSimpleName(), configAttributesList)); } @@ -400,11 +405,40 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot ActiveProfilesUtils.resolveActiveProfiles(testClass), mergedTestPropertySources.getLocations(), mergedTestPropertySources.getProperties(), - contextLoader, cacheAwareContextLoaderDelegate, parentConfig); + contextCustomizers, contextLoader, cacheAwareContextLoaderDelegate, parentConfig); return processMergedContextConfiguration(mergedConfig); } + /** + * @since 4.3 + */ + private Set getContextCustomizers(Class testClass, + List configAttributes) { + + List factories = getContextCustomizerFactories(); + Set customizers = new LinkedHashSet(factories.size()); + for (ContextCustomizerFactory factory : factories) { + ContextCustomizer customizer = factory.createContextCustomizer(testClass, configAttributes); + if (customizer != null) { + customizers.add(customizer); + } + } + return customizers; + } + + /** + * Get the {@link ContextCustomizerFactory} instances for this bootstrapper. + *

    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 getContextCustomizerFactories() { + return SpringFactoriesLoader.loadFactories(ContextCustomizerFactory.class, getClass().getClassLoader()); + } + /** * @since 4.3 */ diff --git a/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java index f6e1ac9e68..a74ca6a2ca 100644 --- a/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java +++ b/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java @@ -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"); * 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}. * * @author Sam Brannen + * @author Phillip Webb * @since 3.2 * @see #loadContext(MergedContextConfiguration) * @see #loadContext(String...) @@ -256,15 +257,17 @@ public abstract class AbstractGenericWebContextLoader extends AbstractContextLoa * loader after bean definitions have been loaded into the context but * before the context is refreshed. * - *

    The default implementation is empty but can be overridden in subclasses - * to customize the web application context. + *

    The default implementation simply delegates to + * {@link AbstractContextLoader#customizeContext(ConfigurableApplicationContext, MergedContextConfiguration)}. * * @param context the newly created web application context * @param webMergedConfig the merged context configuration to use to load the * web application context * @see #loadContext(MergedContextConfiguration) + * @see #customizeContext(ConfigurableApplicationContext, MergedContextConfiguration) */ protected void customizeContext(GenericWebApplicationContext context, WebMergedContextConfiguration webMergedConfig) { + super.customizeContext(context, webMergedConfig); } // --- ContextLoader ------------------------------------------------------- diff --git a/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java index cd1187b535..98bbdad2a9 100644 --- a/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java @@ -22,6 +22,7 @@ import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.style.ToStringCreator; import org.springframework.test.context.CacheAwareContextLoaderDelegate; +import org.springframework.test.context.ContextCustomizer; import org.springframework.test.context.ContextLoader; import org.springframework.test.context.MergedContextConfiguration; import org.springframework.util.ObjectUtils; @@ -132,13 +133,48 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration { String resourceBasePath, ContextLoader contextLoader, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) { + this(testClass, locations, classes, contextInitializerClasses, activeProfiles, propertySourceLocations, + propertySourceProperties, null, resourceBasePath, contextLoader, cacheAwareContextLoaderDelegate, parent); + } + + /** + * Create a new {@code WebMergedContextConfiguration} instance for the + * supplied parameters. + *

    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 empty 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>> contextInitializerClasses, + String[] activeProfiles, String[] propertySourceLocations, String[] propertySourceProperties, + Set contextCustomizers, String resourceBasePath, ContextLoader contextLoader, + CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) { + super(testClass, locations, classes, contextInitializerClasses, activeProfiles, propertySourceLocations, - propertySourceProperties, contextLoader, cacheAwareContextLoaderDelegate, parent); + propertySourceProperties, contextCustomizers, contextLoader, cacheAwareContextLoaderDelegate, parent); - this.resourceBasePath = !StringUtils.hasText(resourceBasePath) ? "" : resourceBasePath; + this.resourceBasePath = (StringUtils.hasText(resourceBasePath) ? resourceBasePath : ""); } - /** * Get the resource path to the root directory of the web application for the * {@linkplain #getTestClass() test class}, configured via {@code @WebAppConfiguration}. @@ -182,6 +218,7 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration { * {@linkplain #getActiveProfiles() active profiles}, * {@linkplain #getPropertySourceLocations() property source locations}, * {@linkplain #getPropertySourceProperties() property source properties}, + * {@linkplain #getContextCustomizers() context customizers}, * {@linkplain #getResourceBasePath() resource base path}, the name of the * {@link #getContextLoader() ContextLoader}, and the * {@linkplain #getParent() parent configuration}. @@ -196,6 +233,7 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration { .append("activeProfiles", ObjectUtils.nullSafeToString(getActiveProfiles())) .append("propertySourceLocations", ObjectUtils.nullSafeToString(getPropertySourceLocations())) .append("propertySourceProperties", ObjectUtils.nullSafeToString(getPropertySourceProperties())) + .append("contextCustomizers", getContextCustomizers()) .append("resourceBasePath", getResourceBasePath()) .append("contextLoader", nullSafeToString(getContextLoader())) .append("parent", getParent()) diff --git a/spring-test/src/test/java/org/springframework/test/context/MergedContextConfigurationTests.java b/spring-test/src/test/java/org/springframework/test/context/MergedContextConfigurationTests.java index bf8eba0403..0409e11f69 100644 --- a/spring-test/src/test/java/org/springframework/test/context/MergedContextConfigurationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/MergedContextConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * 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. @@ -16,6 +16,7 @@ package org.springframework.test.context; +import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -28,6 +29,7 @@ import org.springframework.test.context.support.AnnotationConfigContextLoader; import org.springframework.test.context.support.GenericXmlContextLoader; import static org.junit.Assert.*; +import static org.mockito.Mockito.*; /** * Unit tests for {@link MergedContextConfiguration}. @@ -37,6 +39,7 @@ import static org.junit.Assert.*; * {@link org.springframework.test.context.cache.ContextCache ContextCache}. * * @author Sam Brannen + * @author Phillip Webb * @since 3.1 */ public class MergedContextConfigurationTests { @@ -400,6 +403,35 @@ public class MergedContextConfigurationTests { assertNotEquals(mergedConfig2, mergedConfig1); } + /** + * @since 4.3 + */ + @Test + public void equalsWithSameContextCustomizers() { + Set customizers = Collections.singleton(mock(ContextCustomizer.class)); + MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY, + 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, customizers, loader, null, null); + assertEquals(mergedConfig1, mergedConfig2); + } + + /** + * @since 4.3 + */ + @Test + public void equalsWithDifferentContextCustomizers() { + Set customizers1 = Collections.singleton(mock(ContextCustomizer.class)); + Set customizers2 = Collections.singleton(mock(ContextCustomizer.class)); + + MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY, + EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, null, null, customizers1, loader, null, null); + MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY, + EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, null, null, customizers2, loader, null, null); + assertNotEquals(mergedConfig1, mergedConfig2); + assertNotEquals(mergedConfig2, mergedConfig1); + } + /** * @since 3.2.2 */ diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/ContextCustomizerSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/ContextCustomizerSpringRunnerTests.java new file mode 100644 index 0000000000..13acb3cd2c --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/ContextCustomizerSpringRunnerTests.java @@ -0,0 +1,65 @@ +/* + * 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.junit4; + +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.BootstrapWith; +import org.springframework.test.context.ContextCustomizer; +import org.springframework.test.context.ContextCustomizerFactory; +import org.springframework.test.context.junit4.ContextCustomizerSpringRunnerTests.CustomTestContextBootstrapper; +import org.springframework.test.context.support.DefaultTestContextBootstrapper; + +import static java.util.Collections.*; +import static org.junit.Assert.*; + +/** + * JUnit 4 based integration test which verifies support of + * {@link ContextCustomizerFactory} and {@link ContextCustomizer}. + * + * @author Sam Brannen + * @author Phillip Webb + * @since 4.3 + */ +@RunWith(SpringRunner.class) +@BootstrapWith(CustomTestContextBootstrapper.class) +public class ContextCustomizerSpringRunnerTests { + + @Autowired String foo; + + + @Test + public void injectedBean() { + assertEquals("foo", foo); + } + + + static class CustomTestContextBootstrapper extends DefaultTestContextBootstrapper { + + @Override + protected List getContextCustomizerFactories() { + return singletonList((testClass, configAttributes) -> + // ContextCustomizer as lambda expression: + (context, mergedConfig) -> context.getBeanFactory().registerSingleton("foo", "foo")); + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsMergedConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsMergedConfigTests.java index 44a780d23f..b499c257a8 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsMergedConfigTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsMergedConfigTests.java @@ -63,7 +63,7 @@ public class BootstrapTestUtilsMergedConfigTests extends AbstractContextConfigur public void buildMergedConfigWithContextConfigurationWithoutLocationsClassesOrInitializers() { exception.expect(IllegalStateException.class); exception.expectMessage(startsWith("DelegatingSmartContextLoader was unable to detect defaults, " - + "and no ApplicationContextInitializers were declared for context configuration attributes")); + + "and no ApplicationContextInitializers or ContextCustomizers were declared for context configuration attributes")); buildMergedContextConfiguration(MissingContextAttributesTestCase.class); }