From 87a3a5cb3b268d69adc6c78a27b4bb002e500609 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Sun, 28 Feb 2016 22:42:17 -0800 Subject: [PATCH 1/2] Introduce ContextCustomizer API in the TestContext Framework Allow third-parties to contribute ContextCustomizers that can customize ApplicationContexts created by the Spring TestContext Framework (TCF) before they are refreshed. A customizer may be provided via a ContextCustomizerFactory which is registered with `spring.factories`. Each factory is consulted whenever a new ApplicationContext needs to be created by the TCF. Factories may inspect various details about the test and either return a new ContextCustomizer or null. ContextCustomizers are similar to ApplicationContextInitializers and may perform any number of tasks, including bean registration, setting of active profiles, etc. Issue: SPR-13998 --- .../test/context/ContextCustomizer.java | 49 +++++++++++ .../context/ContextCustomizerFactory.java | 46 ++++++++++ .../context/MergedContextConfiguration.java | 53 ++++++++++- .../support/AbstractContextLoader.java | 23 +++++ .../AbstractDelegatingSmartContextLoader.java | 2 +- .../support/AbstractGenericContextLoader.java | 1 + .../AbstractTestContextBootstrapper.java | 31 ++++++- .../web/AbstractGenericWebContextLoader.java | 4 +- .../MergedContextConfigurationTests.java | 34 ++++++- ...stContextManagerVerifyAttributesTests.java | 71 +++++++++++++++ .../ContextCustomizerSpringRunnerTests.java | 88 +++++++++++++++++++ .../BootstrapTestUtilsMergedConfigTests.java | 2 +- 12 files changed, 392 insertions(+), 12 deletions(-) create mode 100644 spring-test/src/main/java/org/springframework/test/context/ContextCustomizer.java create mode 100644 spring-test/src/main/java/org/springframework/test/context/ContextCustomizerFactory.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/TestContextManagerVerifyAttributesTests.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit4/ContextCustomizerSpringRunnerTests.java 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..05e2c09dd0 --- /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.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * Strategy interface for customizing {@link ApplicationContext application contexts} that + * are created and managed by the Spring TestContext Framework. + * + *

Customizers are loaded via {@link ContextCustomizerFactory} classes registered in + * {@code spring.factories}. + * + *

Implementations should take care to 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 + * @since 4.3 + * @see ContextCustomizerFactory + * @see org.springframework.test.context.support.AbstractContextLoader + */ +public interface ContextCustomizer { + + /** + * Called before bean definitions are read to customize the + * {@link ConfigurableApplicationContext}. + * @param context the context that should be prepared + * @param mergedContextConfiguration the merged context configuration + */ + void customizeContext(ConfigurableApplicationContext context, + MergedContextConfiguration mergedContextConfiguration); + +} 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..f9c77277b3 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/ContextCustomizerFactory.java @@ -0,0 +1,46 @@ +/* + * 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; + +import org.springframework.context.ConfigurableApplicationContext; + +/** + * Factory registered in {@code spring.factories} that is used to create + * {@link ContextCustomizer ContextCustomizers}. Factories are called after + * {@link ContextLoader ContextLoaders} have been triggered but before the + * {@link MergedContextConfiguration} is created. + * + * @author Phillip Webb + * @since 4.3 + */ +public interface ContextCustomizerFactory { + + /** + * Get the {@link ContextCustomizer} (if any) that should be used to customize the + * {@link ConfigurableApplicationContext} when it is created. + * @param testClass the test class + * @param configurationAttributes he 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} + */ + ContextCustomizer getContextCustomizer(Class testClass, + List configurationAttributes); + +} 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..1476f5863b 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 @@ -89,6 +89,8 @@ public class MergedContextConfiguration implements Serializable { private final String[] propertySourceProperties; + private final Set contextCustomizers; + private final ContextLoader contextLoader; private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate; @@ -201,8 +203,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 +235,40 @@ 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 the + * {@code contextInitializerClasses} 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 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.2 + */ + 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 +277,7 @@ public class MergedContextConfiguration implements Serializable { this.activeProfiles = processActiveProfiles(activeProfiles); this.propertySourceLocations = processStrings(propertySourceLocations); this.propertySourceProperties = processStrings(propertySourceProperties); + this.contextCustomizers = Collections.unmodifiableSet(contextCustomizers); this.contextLoader = contextLoader; this.cacheAwareContextLoaderDelegate = cacheAwareContextLoaderDelegate; this.parent = parent; @@ -348,6 +385,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 contextCustomizers; + } + /** * Get the resolved {@link ContextLoader} for the {@linkplain #getTestClass() test class}. */ @@ -424,6 +469,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 +502,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; 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..279bccf2d6 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 @@ -28,11 +28,13 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextException; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.GenericTypeResolver; 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; @@ -107,6 +109,8 @@ public abstract class AbstractContextLoader implements SmartContextLoader { * {@linkplain MergedContextConfiguration#getPropertySourceProperties() * inlined properties} from the supplied {@code MergedContextConfiguration} * to the {@code Environment} of the context. + *

  • Calls any {@link MergedContextConfiguration#getContextCustomizers() + * ContextCustomizers} that are part of the {@link MergedContextConfiguration}.
  • *
  • Determines what (if any) context initializer classes have been supplied * via the {@code MergedContextConfiguration} and instantiates and * {@linkplain ApplicationContextInitializer#initialize invokes} each with the @@ -167,6 +171,25 @@ 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 is refreshed. + * + *

    The default implementation triggers all the + * {@link MergedContextConfiguration#getContextCustomizers() context customizers} that + * have been registered with the {@code mergedConfig}. + * + * @param context the newly created application context + * @param mergedConfig the merged context configuration + * @since 4.3 + */ + protected void customizeContext(GenericApplicationContext 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..4fb04597cb 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 @@ -254,7 +254,7 @@ 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()) { + 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..3b924f0879 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 @@ -122,6 +122,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader loadBeanDefinitions(context, mergedConfig); AnnotationConfigUtils.registerAnnotationConfigProcessors(context); customizeContext(context); + customizeContext(context, mergedConfig); context.refresh(); context.registerShutdownHook(); return 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..77e375cb72 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,14 +405,32 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot ActiveProfilesUtils.resolveActiveProfiles(testClass), mergedTestPropertySources.getLocations(), mergedTestPropertySources.getProperties(), - contextLoader, cacheAwareContextLoaderDelegate, parentConfig); + contextCustomizers, contextLoader, cacheAwareContextLoaderDelegate, parentConfig); return processMergedContextConfiguration(mergedConfig); } + private Set getContextCustomizers(Class testClass, + List configurationAttributes) { + List factories = geContextCustomizerFactories(); + Set customizers = new LinkedHashSet(factories.size()); + for (ContextCustomizerFactory factory : factories) { + ContextCustomizer customizer = factory.getContextCustomizer(testClass, configurationAttributes); + if (customizer != null) { + customizers.add(customizer); + } + } + return customizers; + } + /** - * @since 4.3 + * Get the default {@link ContextCustomizerFactory} instances for this bootstrapper. */ + protected List geContextCustomizerFactories() { + return SpringFactoriesLoader.loadFactories(ContextCustomizerFactory.class, + getClass().getClassLoader()); + } + private boolean areAllEmpty(Collection... collections) { for (Collection collection : collections) { if (!collection.isEmpty()) { 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..53a3b7250f 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 @@ -256,15 +256,13 @@ 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. - * * @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) */ protected void customizeContext(GenericWebApplicationContext context, WebMergedContextConfiguration webMergedConfig) { + super.customizeContext(context, webMergedConfig); } // --- ContextLoader ------------------------------------------------------- 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..733f7c0c3b 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}. @@ -400,6 +402,36 @@ public class MergedContextConfigurationTests { assertNotEquals(mergedConfig2, mergedConfig1); } + @Test + public void equalsWithSameContextCustomizers() { + Set customizers1 = 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, customizers1, loader, null, null); + assertEquals(mergedConfig1, mergedConfig2); + } + + @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/TestContextManagerVerifyAttributesTests.java b/spring-test/src/test/java/org/springframework/test/context/TestContextManagerVerifyAttributesTests.java new file mode 100644 index 0000000000..72d83a38c6 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/TestContextManagerVerifyAttributesTests.java @@ -0,0 +1,71 @@ +/* + * 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 { + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + } + + } +} 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..c978c4977d --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/ContextCustomizerSpringRunnerTests.java @@ -0,0 +1,88 @@ +/* + * 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.Collections; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ConfigurableApplicationContext; +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.ContextCustomizerFactory; +import org.springframework.test.context.MergedContextConfiguration; +import org.springframework.test.context.junit4.ContextCustomizerSpringRunnerTests.CustomTestContextBootstrapper; +import org.springframework.test.context.support.DefaultTestContextBootstrapper; + +import static org.junit.Assert.*; + +/** + * JUnit 4 based integration test which verifies support of + * {@link ContextCustomizerFactory} and {@link ContextCustomizer}. + * + * @author Phillip Webb + * @since 4.3 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@BootstrapWith(CustomTestContextBootstrapper.class) +@ContextConfiguration +public class ContextCustomizerSpringRunnerTests { + + @Autowired + private MyBean myBean; + + @Test + public void injectedMyBean() throws Exception { + assertNotNull(this.myBean); + } + + public static class CustomTestContextBootstrapper + extends DefaultTestContextBootstrapper { + + @Override + protected List geContextCustomizerFactories() { + return Collections.singletonList(new ContextCustomizerFactory() { + + @Override + public ContextCustomizer getContextCustomizer(Class testClass, + List 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 { + } + +} 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..1b509fc92e 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 attribute")); buildMergedContextConfiguration(MissingContextAttributesTestCase.class); } From cbd1342fbb249e36a85c47847842606b935934fb Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 11 Mar 2016 20:52:10 +0100 Subject: [PATCH 2/2] Polish ContextCustomizer support in the TCF Issue: SPR-13998 --- .../test/context/ContextCustomizer.java | 30 ++++---- .../context/ContextCustomizerFactory.java | 32 +++++---- .../context/MergedContextConfiguration.java | 21 ++++-- .../support/AbstractContextLoader.java | 20 +++--- .../AbstractDelegatingSmartContextLoader.java | 3 +- .../support/AbstractGenericContextLoader.java | 6 +- .../AbstractTestContextBootstrapper.java | 25 +++++-- .../web/AbstractGenericWebContextLoader.java | 7 +- .../web/WebMergedContextConfiguration.java | 44 +++++++++++- .../MergedContextConfigurationTests.java | 38 +++++----- ...stContextManagerVerifyAttributesTests.java | 71 ------------------- .../ContextCustomizerSpringRunnerTests.java | 49 ++++--------- .../BootstrapTestUtilsMergedConfigTests.java | 2 +- 13 files changed, 164 insertions(+), 184 deletions(-) delete mode 100644 spring-test/src/test/java/org/springframework/test/context/TestContextManagerVerifyAttributesTests.java 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 index 05e2c09dd0..d9462faadb 100644 --- a/spring-test/src/main/java/org/springframework/test/context/ContextCustomizer.java +++ b/spring-test/src/main/java/org/springframework/test/context/ContextCustomizer.java @@ -16,34 +16,34 @@ package org.springframework.test.context; -import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; /** - * Strategy interface for customizing {@link ApplicationContext application contexts} that - * are created and managed by the Spring TestContext Framework. + * Strategy interface for customizing {@link ConfigurableApplicationContext + * application contexts} that are created and managed by the Spring + * TestContext Framework. * - *

    Customizers are loaded via {@link ContextCustomizerFactory} classes registered in - * {@code spring.factories}. + *

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

    Implementations should take care to implement correct {@code equals} and - * {@code hashCode} methods since customizers form part of the - * {@link MergedContextConfiguration} which is used as a cache key. + *

    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 + * @see org.springframework.test.context.support.AbstractContextLoader#customizeContext */ public interface ContextCustomizer { /** - * Called before bean definitions are read to customize the - * {@link ConfigurableApplicationContext}. - * @param context the context that should be prepared - * @param mergedContextConfiguration the merged context configuration + * 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 mergedContextConfiguration); + 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 index f9c77277b3..9f07700a38 100644 --- a/spring-test/src/main/java/org/springframework/test/context/ContextCustomizerFactory.java +++ b/spring-test/src/main/java/org/springframework/test/context/ContextCustomizerFactory.java @@ -18,29 +18,35 @@ package org.springframework.test.context; import java.util.List; -import org.springframework.context.ConfigurableApplicationContext; - /** - * Factory registered in {@code spring.factories} that is used to create - * {@link ContextCustomizer ContextCustomizers}. Factories are called after - * {@link ContextLoader ContextLoaders} have been triggered but before the + * 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 { /** - * Get the {@link ContextCustomizer} (if any) that should be used to customize the - * {@link ConfigurableApplicationContext} when it is created. + * 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 configurationAttributes he 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} + * @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 getContextCustomizer(Class testClass, - List configurationAttributes); + 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 1476f5863b..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; @@ -113,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; @@ -247,8 +255,8 @@ public class MergedContextConfiguration implements Serializable { *

    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 the - * {@code contextInitializerClasses} an empty set 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 @@ -258,11 +266,12 @@ public class MergedContextConfiguration implements Serializable { * @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.2 + * @since 4.3 */ public MergedContextConfiguration(Class testClass, String[] locations, Class[] classes, Set>> contextInitializerClasses, @@ -277,7 +286,7 @@ public class MergedContextConfiguration implements Serializable { this.activeProfiles = processActiveProfiles(activeProfiles); this.propertySourceLocations = processStrings(propertySourceLocations); this.propertySourceProperties = processStrings(propertySourceProperties); - this.contextCustomizers = Collections.unmodifiableSet(contextCustomizers); + this.contextCustomizers = processContextCustomizers(contextCustomizers); this.contextLoader = contextLoader; this.cacheAwareContextLoaderDelegate = cacheAwareContextLoaderDelegate; this.parent = parent; @@ -390,7 +399,7 @@ public class MergedContextConfiguration implements Serializable { * when the application context is loaded. */ public Set getContextCustomizers() { - return contextCustomizers; + return this.contextCustomizers; } /** @@ -515,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}. */ @@ -528,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 279bccf2d6..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 @@ -28,7 +28,6 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextException; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.GenericTypeResolver; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.env.PropertySource; @@ -58,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 { @@ -109,8 +111,6 @@ public abstract class AbstractContextLoader implements SmartContextLoader { * {@linkplain MergedContextConfiguration#getPropertySourceProperties() * inlined properties} from the supplied {@code MergedContextConfiguration} * to the {@code Environment} of the context.

  • - *
  • Calls any {@link MergedContextConfiguration#getContextCustomizers() - * ContextCustomizers} that are part of the {@link MergedContextConfiguration}.
  • *
  • Determines what (if any) context initializer classes have been supplied * via the {@code MergedContextConfiguration} and instantiates and * {@linkplain ApplicationContextInitializer#initialize invokes} each with the @@ -173,18 +173,16 @@ 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 is refreshed. - * - *

    The default implementation triggers all the - * {@link MergedContextConfiguration#getContextCustomizers() context customizers} that - * have been registered with the {@code mergedConfig}. - * + * {@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(GenericApplicationContext context, MergedContextConfiguration mergedConfig) { + protected void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) { for (ContextCustomizer contextCustomizer : mergedConfig.getContextCustomizers()) { contextCustomizer.customizeContext(context, mergedConfig); } 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 4fb04597cb..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,7 +253,8 @@ 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. + // 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 3b924f0879..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.
  • * @@ -206,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) { @@ -279,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 77e375cb72..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 @@ -410,12 +410,16 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot return processMergedContextConfiguration(mergedConfig); } + /** + * @since 4.3 + */ private Set getContextCustomizers(Class testClass, - List configurationAttributes) { - List factories = geContextCustomizerFactories(); + List configAttributes) { + + List factories = getContextCustomizerFactories(); Set customizers = new LinkedHashSet(factories.size()); for (ContextCustomizerFactory factory : factories) { - ContextCustomizer customizer = factory.getContextCustomizer(testClass, configurationAttributes); + ContextCustomizer customizer = factory.createContextCustomizer(testClass, configAttributes); if (customizer != null) { 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. + *

    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 geContextCustomizerFactories() { - return SpringFactoriesLoader.loadFactories(ContextCustomizerFactory.class, - getClass().getClassLoader()); + protected List getContextCustomizerFactories() { + return SpringFactoriesLoader.loadFactories(ContextCustomizerFactory.class, getClass().getClassLoader()); } + /** + * @since 4.3 + */ private boolean areAllEmpty(Collection... collections) { for (Collection collection : collections) { if (!collection.isEmpty()) { 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 53a3b7250f..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,10 +257,14 @@ 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 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); 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 733f7c0c3b..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 @@ -39,6 +39,7 @@ import static org.mockito.Mockito.*; * {@link org.springframework.test.context.cache.ContextCache ContextCache}. * * @author Sam Brannen + * @author Phillip Webb * @since 3.1 */ public class MergedContextConfigurationTests { @@ -402,32 +403,31 @@ public class MergedContextConfigurationTests { assertNotEquals(mergedConfig2, mergedConfig1); } + /** + * @since 4.3 + */ @Test public void equalsWithSameContextCustomizers() { - Set customizers1 = 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, customizers1, loader, null, null); + 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); + 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); } diff --git a/spring-test/src/test/java/org/springframework/test/context/TestContextManagerVerifyAttributesTests.java b/spring-test/src/test/java/org/springframework/test/context/TestContextManagerVerifyAttributesTests.java deleted file mode 100644 index 72d83a38c6..0000000000 --- a/spring-test/src/test/java/org/springframework/test/context/TestContextManagerVerifyAttributesTests.java +++ /dev/null @@ -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 { - - @Override - public void initialize(ConfigurableApplicationContext applicationContext) { - } - - } -} 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 index c978c4977d..13acb3cd2c 100644 --- 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 @@ -16,73 +16,50 @@ package org.springframework.test.context.junit4; -import java.util.Collections; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; + import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ConfigurableApplicationContext; 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.ContextCustomizerFactory; -import org.springframework.test.context.MergedContextConfiguration; 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(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @BootstrapWith(CustomTestContextBootstrapper.class) -@ContextConfiguration public class ContextCustomizerSpringRunnerTests { - @Autowired - private MyBean myBean; + @Autowired String foo; + @Test - public void injectedMyBean() throws Exception { - assertNotNull(this.myBean); + public void injectedBean() { + assertEquals("foo", foo); } - public static class CustomTestContextBootstrapper - extends DefaultTestContextBootstrapper { - - @Override - protected List geContextCustomizerFactories() { - return Collections.singletonList(new ContextCustomizerFactory() { - - @Override - public ContextCustomizer getContextCustomizer(Class testClass, - List configurationAttributes) { - return new TestContextCustomizers(); - } - - }); - } - - } - public static class TestContextCustomizers implements ContextCustomizer { + static class CustomTestContextBootstrapper extends DefaultTestContextBootstrapper { @Override - public void customizeContext(ConfigurableApplicationContext context, - MergedContextConfiguration mergedContextConfiguration) { - context.getBeanFactory().registerSingleton("mybean", new MyBean()); + protected List getContextCustomizerFactories() { + return singletonList((testClass, configAttributes) -> + // ContextCustomizer as lambda expression: + (context, mergedConfig) -> context.getBeanFactory().registerSingleton("foo", "foo")); } - - } - - public static class MyBean { } } 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 1b509fc92e..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 or ContextCustomizers were declared for context configuration attribute")); + + "and no ApplicationContextInitializers or ContextCustomizers were declared for context configuration attributes")); buildMergedContextConfiguration(MissingContextAttributesTestCase.class); }