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);
}