diff --git a/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java b/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java
index c54e7c2d68b..c0f73c3fe5d 100644
--- a/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java
+++ b/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2014 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.
@@ -50,18 +50,16 @@ public @interface ActiveProfiles {
/**
* Alias for {@link #profiles}.
*
- *
This attribute may not be used in conjunction
- * with {@link #profiles} or {@link #resolver}, but it may be used
- * instead of them.
+ *
This attribute may not be used in conjunction with
+ * {@link #profiles}, but it may be used instead of {@link #profiles}.
*/
String[] value() default {};
/**
* The bean definition profiles to activate.
*
- *
This attribute may not be used in conjunction
- * with {@link #value} or {@link #resolver}, but it may be used
- * instead of them.
+ *
This attribute may not be used in conjunction with
+ * {@link #value}, but it may be used instead of {@link #value}.
*/
String[] profiles() default {};
@@ -69,10 +67,6 @@ public @interface ActiveProfiles {
* The type of {@link ActiveProfilesResolver} to use for resolving the active
* bean definition profiles programmatically.
*
- *
This attribute may not be used in conjunction
- * with {@link #profiles} or {@link #value}, but it may be used instead
- * of them in order to resolve the active profiles programmatically.
- *
* @since 4.0
* @see ActiveProfilesResolver
*/
diff --git a/spring-test/src/main/java/org/springframework/test/context/BootstrapContext.java b/spring-test/src/main/java/org/springframework/test/context/BootstrapContext.java
new file mode 100644
index 00000000000..fa12a797e34
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/BootstrapContext.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-2014 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;
+
+/**
+ * {@code BootstrapContext} encapsulates the context in which the Spring
+ * TestContext Framework is bootstrapped.
+ *
+ * @author Sam Brannen
+ * @since 4.1
+ * @see BootstrapWith
+ * @see TestContextBootstrapper
+ */
+public interface BootstrapContext {
+
+ /**
+ * Get the {@link Class test class} for this bootstrap context.
+ * @return the test class (never {@code null})
+ */
+ Class> getTestClass();
+
+ /**
+ * Get the {@link CacheAwareContextLoaderDelegate} to use for transparent
+ * interaction with the context cache.
+ */
+ CacheAwareContextLoaderDelegate getCacheAwareContextLoaderDelegate();
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java b/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java
new file mode 100644
index 00000000000..6ef59cbcf44
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2002-2014 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.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.util.ClassUtils;
+
+import static org.springframework.beans.BeanUtils.*;
+import static org.springframework.core.annotation.AnnotationUtils.*;
+
+/**
+ * {@code BootstrapUtils} is a collection of utility methods to assist with
+ * bootstrapping the Spring TestContext Framework.
+ *
+ * @author Sam Brannen
+ * @since 4.1
+ * @see BootstrapWith
+ * @see BootstrapContext
+ * @see TestContextBootstrapper
+ */
+abstract class BootstrapUtils {
+
+ private static final String DEFAULT_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME = "org.springframework.test.context.support.DefaultTestContextBootstrapper";
+
+ private static final Log logger = LogFactory.getLog(BootstrapUtils.class);
+
+
+ private BootstrapUtils() {
+ /* no-op */
+ }
+
+ /**
+ * Resolve the {@link TestContextBootstrapper} type for the test class in the
+ * supplied {@link BootstrapContext}, instantiate it, and provide it a reference
+ * to the {@link BootstrapContext}.
+ *
+ *
If the {@link BootstrapWith @BootstrapWith} annotation is present on
+ * the test class, either directly or as a meta-annotation, then its
+ * {@link BootstrapWith#value value} will be used as the bootstrapper type.
+ * Otherwise, the {@link org.springframework.test.context.support.DefaultTestContextBootstrapper
+ * DefaultTestContextBootstrapper} will be used.
+ *
+ * @param bootstrapContext the bootstrap context to use
+ * @return a fully configured {@code TestContextBootstrapper}
+ */
+ @SuppressWarnings("unchecked")
+ static TestContextBootstrapper resolveTestContextBootstrapper(BootstrapContext bootstrapContext) {
+ Class> testClass = bootstrapContext.getTestClass();
+
+ Class extends TestContextBootstrapper> clazz = null;
+ try {
+ BootstrapWith bootstrapWith = findAnnotation(testClass, BootstrapWith.class);
+ if (bootstrapWith != null && !TestContextBootstrapper.class.equals(bootstrapWith.value())) {
+ clazz = bootstrapWith.value();
+ }
+ else {
+ clazz = (Class extends TestContextBootstrapper>) ClassUtils.forName(
+ DEFAULT_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME, BootstrapUtils.class.getClassLoader());
+ }
+
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Instantiating TestContextBootstrapper from class [%s]", clazz.getName()));
+ }
+
+ TestContextBootstrapper testContextBootstrapper = instantiateClass(clazz, TestContextBootstrapper.class);
+ testContextBootstrapper.setBootstrapContext(bootstrapContext);
+
+ return testContextBootstrapper;
+ }
+ catch (Throwable t) {
+ throw new IllegalStateException("Could not load TestContextBootstrapper [" + clazz
+ + "]. Specify @BootstrapWith's 'value' attribute "
+ + "or make the default bootstrapper class available.", t);
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/BootstrapWith.java b/spring-test/src/main/java/org/springframework/test/context/BootstrapWith.java
new file mode 100644
index 00000000000..bfd32c89316
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/BootstrapWith.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2002-2014 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.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * {@code @BootstrapWith} defines class-level metadata that is used to determine
+ * how to bootstrap the Spring TestContext Framework.
+ *
+ *
This annotation may also be used as a meta-annotation to create
+ * custom composed annotations.
+ *
+ * @author Sam Brannen
+ * @since 4.1
+ * @see BootstrapContext
+ * @see TestContextBootstrapper
+ */
+@Documented
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface BootstrapWith {
+
+ /**
+ * The {@link TestContextBootstrapper} to use to bootstrap the Spring
+ * TestContext Framework.
+ */
+ Class extends TestContextBootstrapper> value() default TestContextBootstrapper.class;
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/CacheAwareContextLoaderDelegate.java b/spring-test/src/main/java/org/springframework/test/context/CacheAwareContextLoaderDelegate.java
index f7375def035..958b210820a 100644
--- a/spring-test/src/main/java/org/springframework/test/context/CacheAwareContextLoaderDelegate.java
+++ b/spring-test/src/main/java/org/springframework/test/context/CacheAwareContextLoaderDelegate.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2014 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,97 +16,61 @@
package org.springframework.test.context;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
-import org.springframework.util.Assert;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
/**
- * {@code CacheAwareContextLoaderDelegate} loads application contexts from
- * {@link MergedContextConfiguration} by delegating to the
- * {@link ContextLoader} configured in the {@code MergedContextConfiguration}
- * and interacting transparently with the {@link ContextCache} behind the scenes.
+ * A {@code CacheAwareContextLoaderDelegate} is responsible for {@linkplain
+ * #loadContext loading} and {@linkplain #closeContext closing} application
+ * contexts, interacting transparently with a context cache behind
+ * the scenes.
*
- *
Note: {@code CacheAwareContextLoaderDelegate} does not implement the
+ *
Note: {@code CacheAwareContextLoaderDelegate} does not extend the
* {@link ContextLoader} or {@link SmartContextLoader} interface.
*
* @author Sam Brannen
* @since 3.2.2
*/
-public class CacheAwareContextLoaderDelegate {
-
- private static final Log logger = LogFactory.getLog(CacheAwareContextLoaderDelegate.class);
-
- private final ContextCache contextCache;
-
-
- CacheAwareContextLoaderDelegate(ContextCache contextCache) {
- Assert.notNull(contextCache, "ContextCache must not be null");
- this.contextCache = contextCache;
- }
+public interface CacheAwareContextLoaderDelegate {
/**
- * Load the {@code ApplicationContext} for the supplied merged context
- * configuration. Supports both the {@link SmartContextLoader} and
- * {@link ContextLoader} SPIs.
- * @throws Exception if an error occurs while loading the application context
- */
- private ApplicationContext loadContextInternal(MergedContextConfiguration mergedContextConfiguration)
- throws Exception {
- ContextLoader contextLoader = mergedContextConfiguration.getContextLoader();
- Assert.notNull(contextLoader, "Cannot load an ApplicationContext with a NULL 'contextLoader'. "
- + "Consider annotating your test class with @ContextConfiguration or @ContextHierarchy.");
-
- ApplicationContext applicationContext;
-
- if (contextLoader instanceof SmartContextLoader) {
- SmartContextLoader smartContextLoader = (SmartContextLoader) contextLoader;
- applicationContext = smartContextLoader.loadContext(mergedContextConfiguration);
- }
- else {
- String[] locations = mergedContextConfiguration.getLocations();
- Assert.notNull(locations, "Cannot load an ApplicationContext with a NULL 'locations' array. "
- + "Consider annotating your test class with @ContextConfiguration or @ContextHierarchy.");
- applicationContext = contextLoader.loadContext(locations);
- }
-
- return applicationContext;
- }
-
- /**
- * Load the {@link ApplicationContext application context} for the supplied
- * merged context configuration.
+ * Load the {@linkplain ApplicationContext application context} for the supplied
+ * {@link MergedContextConfiguration} by delegating to the {@link ContextLoader}
+ * configured in the given {@code MergedContextConfiguration}.
+ *
+ *
If the context is present in the context cache it will simply
+ * be returned; otherwise, it will be loaded, stored in the cache, and returned.
*
- *
If the context is present in the cache it will simply be returned;
- * otherwise, it will be loaded, stored in the cache, and returned.
+ * @param mergedContextConfiguration the merged context configuration to use
+ * to load the application context; never {@code null}
* @return the application context
* @throws IllegalStateException if an error occurs while retrieving or
* loading the application context
*/
- public ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration) {
- synchronized (contextCache) {
- ApplicationContext context = contextCache.get(mergedContextConfiguration);
- if (context == null) {
- try {
- context = loadContextInternal(mergedContextConfiguration);
- if (logger.isDebugEnabled()) {
- logger.debug(String.format("Storing ApplicationContext in cache under key [%s].",
- mergedContextConfiguration));
- }
- contextCache.put(mergedContextConfiguration, context);
- }
- catch (Exception ex) {
- throw new IllegalStateException("Failed to load ApplicationContext", ex);
- }
- }
- else {
- if (logger.isDebugEnabled()) {
- logger.debug(String.format("Retrieved ApplicationContext from cache with key [%s].",
- mergedContextConfiguration));
- }
- }
- return context;
- }
- }
+ ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration);
+
+ /**
+ * Remove the {@linkplain ApplicationContext application context} for the
+ * supplied {@link MergedContextConfiguration} from the context cache
+ * and {@linkplain ConfigurableApplicationContext#close() close} it if it is
+ * an instance of {@link ConfigurableApplicationContext}.
+ *
+ *
The semantics of the supplied {@code HierarchyMode} must be honored when
+ * removing the context from the cache. See the Javadoc for {@link HierarchyMode}
+ * for details.
+ *
+ *
Generally speaking, this method should only be called if the state of
+ * a singleton bean has been changed (potentially affecting future interaction
+ * with the context) or if the context needs to be prematurely removed from
+ * the cache.
+ *
+ * @param mergedContextConfiguration the merged context configuration for the
+ * application context to close; never {@code null}
+ * @param hierarchyMode the hierarchy mode; may be {@code null} if the context
+ * is not part of a hierarchy
+ * @since 4.1
+ */
+ void closeContext(MergedContextConfiguration mergedContextConfiguration, HierarchyMode hierarchyMode);
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java
index 1b3ea9f2741..a716284a90a 100644
--- a/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java
+++ b/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java
@@ -91,6 +91,7 @@ public @interface ContextConfiguration {
*
*
This attribute may not be used in conjunction with
* {@link #locations}, but it may be used instead of {@link #locations}.
+ *
* @since 3.0
* @see #inheritLocations
*/
@@ -120,6 +121,7 @@ public @interface ContextConfiguration {
*
*
This attribute may not be used in conjunction with
* {@link #value}, but it may be used instead of {@link #value}.
+ *
* @since 2.5
* @see #inheritLocations
*/
diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java b/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java
deleted file mode 100644
index a3bd700d35d..00000000000
--- a/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java
+++ /dev/null
@@ -1,807 +0,0 @@
-/*
- * Copyright 2002-2014 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.lang.annotation.Annotation;
-import java.lang.reflect.Constructor;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.springframework.context.ApplicationContextInitializer;
-import org.springframework.context.ConfigurableApplicationContext;
-import org.springframework.core.annotation.AnnotationAttributes;
-import org.springframework.core.annotation.AnnotationUtils;
-import org.springframework.test.context.MetaAnnotationUtils.AnnotationDescriptor;
-import org.springframework.test.context.MetaAnnotationUtils.UntypedAnnotationDescriptor;
-import org.springframework.util.Assert;
-import org.springframework.util.ClassUtils;
-import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
-
-import static org.springframework.beans.BeanUtils.*;
-import static org.springframework.core.annotation.AnnotationUtils.*;
-import static org.springframework.test.context.MetaAnnotationUtils.*;
-
-/**
- * Utility methods for working with {@link ContextLoader ContextLoaders} and
- * {@link SmartContextLoader SmartContextLoaders} and resolving resource locations,
- * annotated classes, active bean definition profiles, and application context
- * initializers.
- *
- * @author Sam Brannen
- * @author Michail Nikolaev
- * @since 3.1
- * @see ContextLoader
- * @see SmartContextLoader
- * @see ContextConfiguration
- * @see ContextConfigurationAttributes
- * @see ActiveProfiles
- * @see ActiveProfilesResolver
- * @see ApplicationContextInitializer
- * @see ContextHierarchy
- * @see MergedContextConfiguration
- */
-abstract class ContextLoaderUtils {
-
- static final String GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX = "ContextHierarchyLevel#";
-
- private static final Log logger = LogFactory.getLog(ContextLoaderUtils.class);
-
- private static final String DEFAULT_CONTEXT_LOADER_CLASS_NAME = "org.springframework.test.context.support.DelegatingSmartContextLoader";
- private static final String DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME = "org.springframework.test.context.web.WebDelegatingSmartContextLoader";
-
- private static final String WEB_APP_CONFIGURATION_CLASS_NAME = "org.springframework.test.context.web.WebAppConfiguration";
- private static final String WEB_MERGED_CONTEXT_CONFIGURATION_CLASS_NAME = "org.springframework.test.context.web.WebMergedContextConfiguration";
-
-
- private ContextLoaderUtils() {
- /* no-op */
- }
-
- /**
- * Resolve the {@link ContextLoader} {@linkplain Class class} to use for the supplied
- * list of {@link ContextConfigurationAttributes} and then instantiate and return that
- * {@code ContextLoader}.
- *
- *
If the supplied {@code defaultContextLoaderClassName} is {@code null} or
- * empty, depending on the absence or presence of
- * {@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration} either
- * {@code "org.springframework.test.context.support.DelegatingSmartContextLoader"} or
- * {@code "org.springframework.test.context.web.WebDelegatingSmartContextLoader"} will
- * be used as the default context loader class name. For details on the class
- * resolution process, see {@link #resolveContextLoaderClass}.
- *
- * @param testClass the test class for which the {@code ContextLoader} should be
- * resolved; must not be {@code null}
- * @param configAttributesList the list of configuration attributes to process; must
- * not be {@code null} or empty; must be ordered bottom-up
- * (i.e., as if we were traversing up the class hierarchy)
- * @param defaultContextLoaderClassName the name of the default {@code ContextLoader}
- * class to use; may be {@code null} or empty
- * @return the resolved {@code ContextLoader} for the supplied {@code testClass}
- * (never {@code null})
- * @see #resolveContextLoaderClass
- */
- static ContextLoader resolveContextLoader(Class> testClass,
- List configAttributesList, String defaultContextLoaderClassName) {
- Assert.notNull(testClass, "Class must not be null");
- Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty");
-
- if (!StringUtils.hasText(defaultContextLoaderClassName)) {
- Class extends Annotation> webAppConfigClass = loadWebAppConfigurationClass();
- defaultContextLoaderClassName = webAppConfigClass != null
- && findAnnotation(testClass, webAppConfigClass) != null ? DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME
- : DEFAULT_CONTEXT_LOADER_CLASS_NAME;
- }
-
- Class extends ContextLoader> contextLoaderClass = resolveContextLoaderClass(testClass, configAttributesList,
- defaultContextLoaderClassName);
-
- return instantiateClass(contextLoaderClass, ContextLoader.class);
- }
-
- /**
- * Resolve the {@link ContextLoader} {@linkplain Class class} to use for the supplied
- * list of {@link ContextConfigurationAttributes}.
- *
- * Beginning with the first level in the context configuration attributes hierarchy:
- *
- *
- * - If the {@link ContextConfigurationAttributes#getContextLoaderClass()
- * contextLoaderClass} property of {@link ContextConfigurationAttributes} is
- * configured with an explicit class, that class will be returned.
- * - If an explicit {@code ContextLoader} class is not specified at the current
- * level in the hierarchy, traverse to the next level in the hierarchy and return to
- * step #1.
- * - If no explicit {@code ContextLoader} class is found after traversing the
- * hierarchy, an attempt will be made to load and return the class with the supplied
- * {@code defaultContextLoaderClassName}.
- *
- *
- * @param testClass the class for which to resolve the {@code ContextLoader} class;
- * must not be {@code null}; only used for logging purposes
- * @param configAttributesList the list of configuration attributes to process; must
- * not be {@code null} or empty; must be ordered bottom-up
- * (i.e., as if we were traversing up the class hierarchy)
- * @param defaultContextLoaderClassName the name of the default {@code ContextLoader}
- * class to use; must not be {@code null} or empty
- * @return the {@code ContextLoader} class to use for the supplied test class
- * @throws IllegalArgumentException if {@code @ContextConfiguration} is not
- * present on the supplied test class
- * @throws IllegalStateException if the default {@code ContextLoader} class could not
- * be loaded
- */
- @SuppressWarnings("unchecked")
- static Class extends ContextLoader> resolveContextLoaderClass(Class> testClass,
- List configAttributesList, String defaultContextLoaderClassName) {
- Assert.notNull(testClass, "Class must not be null");
- Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty");
- Assert.hasText(defaultContextLoaderClassName, "Default ContextLoader class name must not be null or empty");
-
- for (ContextConfigurationAttributes configAttributes : configAttributesList) {
- if (logger.isTraceEnabled()) {
- logger.trace(String.format("Processing ContextLoader for context configuration attributes %s",
- configAttributes));
- }
-
- Class extends ContextLoader> contextLoaderClass = configAttributes.getContextLoaderClass();
- if (!ContextLoader.class.equals(contextLoaderClass)) {
- if (logger.isDebugEnabled()) {
- logger.debug(String.format(
- "Found explicit ContextLoader class [%s] for context configuration attributes %s",
- contextLoaderClass.getName(), configAttributes));
- }
- return contextLoaderClass;
- }
- }
-
- try {
- if (logger.isTraceEnabled()) {
- logger.trace(String.format("Using default ContextLoader class [%s] for test class [%s]",
- defaultContextLoaderClassName, testClass.getName()));
- }
- return (Class extends ContextLoader>) ClassUtils.forName(defaultContextLoaderClassName,
- ContextLoaderUtils.class.getClassLoader());
- }
- catch (Throwable t) {
- throw new IllegalStateException("Could not load default ContextLoader class ["
- + defaultContextLoaderClassName + "]. Specify @ContextConfiguration's 'loader' "
- + "attribute or make the default loader class available.", t);
- }
- }
-
- /**
- * Convenience method for creating a {@link ContextConfigurationAttributes}
- * instance from the supplied {@link ContextConfiguration} annotation and
- * declaring class and then adding the attributes to the supplied list.
- */
- private static void convertContextConfigToConfigAttributesAndAddToList(ContextConfiguration contextConfiguration,
- Class> declaringClass, final List attributesList) {
- if (logger.isTraceEnabled()) {
- logger.trace(String.format("Retrieved @ContextConfiguration [%s] for declaring class [%s].",
- contextConfiguration, declaringClass.getName()));
- }
-
- ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(declaringClass,
- contextConfiguration);
- if (logger.isTraceEnabled()) {
- logger.trace("Resolved context configuration attributes: " + attributes);
- }
- attributesList.add(attributes);
- }
-
- /**
- * Convenience method for creating a {@link ContextConfigurationAttributes}
- * instance from the supplied {@link AnnotationAttributes} and declaring
- * class and then adding the attributes to the supplied list.
- * @since 4.0
- */
- private static void convertAnnotationAttributesToConfigAttributesAndAddToList(AnnotationAttributes annAttrs,
- Class> declaringClass, final List attributesList) {
- if (logger.isTraceEnabled()) {
- logger.trace(String.format("Retrieved @ContextConfiguration attributes [%s] for declaring class [%s].",
- annAttrs, declaringClass.getName()));
- }
-
- ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(declaringClass, annAttrs);
- if (logger.isTraceEnabled()) {
- logger.trace("Resolved context configuration attributes: " + attributes);
- }
- attributesList.add(attributes);
- }
-
- /**
- * Resolve the list of lists of {@linkplain ContextConfigurationAttributes context
- * configuration attributes} for the supplied {@linkplain Class test class} and its
- * superclasses, taking into account context hierarchies declared via
- * {@link ContextHierarchy @ContextHierarchy} and
- * {@link ContextConfiguration @ContextConfiguration}.
- *
- * The outer list represents a top-down ordering of context configuration
- * attributes, where each element in the list represents the context configuration
- * declared on a given test class in the class hierarchy. Each nested list
- * contains the context configuration attributes declared either via a single
- * instance of {@code @ContextConfiguration} on the particular class or via
- * multiple instances of {@code @ContextConfiguration} declared within a
- * single {@code @ContextHierarchy} instance on the particular class.
- * Furthermore, each nested list maintains the order in which
- * {@code @ContextConfiguration} instances are declared.
- *
- *
Note that the {@link ContextConfiguration#inheritLocations inheritLocations} and
- * {@link ContextConfiguration#inheritInitializers() inheritInitializers} flags of
- * {@link ContextConfiguration @ContextConfiguration} will not
- * be taken into consideration. If these flags need to be honored, that must be
- * handled manually when traversing the nested lists returned by this method.
- *
- * @param testClass the class for which to resolve the context hierarchy attributes
- * (must not be {@code null})
- * @return the list of lists of configuration attributes for the specified class;
- * never {@code null}
- * @throws IllegalArgumentException if the supplied class is {@code null}; if
- * neither {@code @ContextConfiguration} nor {@code @ContextHierarchy} is
- * present on the supplied class; or if a test class or composed annotation
- * in the class hierarchy declares both {@code @ContextConfiguration} and
- * {@code @ContextHierarchy} as top-level annotations.
- * @throws IllegalStateException if no class in the class hierarchy declares
- * {@code @ContextHierarchy}.
- *
- * @since 3.2.2
- * @see #buildContextHierarchyMap(Class)
- * @see #resolveContextConfigurationAttributes(Class)
- */
- @SuppressWarnings("unchecked")
- static List> resolveContextHierarchyAttributes(Class> testClass) {
- Assert.notNull(testClass, "Class must not be null");
- Assert.state(findAnnotation(testClass, ContextHierarchy.class) != null, "@ContextHierarchy must be present");
-
- final Class contextConfigType = ContextConfiguration.class;
- final Class contextHierarchyType = ContextHierarchy.class;
- final List> hierarchyAttributes = new ArrayList>();
-
- UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(testClass, contextConfigType,
- contextHierarchyType);
- Assert.notNull(descriptor, String.format(
- "Could not find an 'annotation declaring class' for annotation type [%s] or [%s] and test class [%s]",
- contextConfigType.getName(), contextHierarchyType.getName(), testClass.getName()));
-
- while (descriptor != null) {
- Class> rootDeclaringClass = descriptor.getRootDeclaringClass();
- Class> declaringClass = descriptor.getDeclaringClass();
-
- boolean contextConfigDeclaredLocally = isAnnotationDeclaredLocally(contextConfigType, declaringClass);
- boolean contextHierarchyDeclaredLocally = isAnnotationDeclaredLocally(contextHierarchyType, declaringClass);
-
- if (contextConfigDeclaredLocally && contextHierarchyDeclaredLocally) {
- String msg = String.format("Class [%s] has been configured with both @ContextConfiguration "
- + "and @ContextHierarchy. Only one of these annotations may be declared on a test class "
- + "or composed annotation.", declaringClass.getName());
- logger.error(msg);
- throw new IllegalStateException(msg);
- }
-
- final List configAttributesList = new ArrayList();
-
- if (contextConfigDeclaredLocally) {
- convertAnnotationAttributesToConfigAttributesAndAddToList(descriptor.getAnnotationAttributes(),
- rootDeclaringClass, configAttributesList);
- }
- else if (contextHierarchyDeclaredLocally) {
- ContextHierarchy contextHierarchy = getAnnotation(declaringClass, contextHierarchyType);
- for (ContextConfiguration contextConfiguration : contextHierarchy.value()) {
- convertContextConfigToConfigAttributesAndAddToList(contextConfiguration, rootDeclaringClass,
- configAttributesList);
- }
- }
- else {
- // This should theoretically never happen...
- String msg = String.format("Test class [%s] has been configured with neither @ContextConfiguration "
- + "nor @ContextHierarchy as a class-level annotation.", rootDeclaringClass.getName());
- logger.error(msg);
- throw new IllegalStateException(msg);
- }
-
- hierarchyAttributes.add(0, configAttributesList);
-
- descriptor = findAnnotationDescriptorForTypes(rootDeclaringClass.getSuperclass(), contextConfigType,
- contextHierarchyType);
- }
-
- return hierarchyAttributes;
- }
-
- /**
- * Build a context hierarchy map for the supplied {@linkplain Class
- * test class} and its superclasses, taking into account context hierarchies
- * declared via {@link ContextHierarchy @ContextHierarchy} and
- * {@link ContextConfiguration @ContextConfiguration}.
- *
- * Each value in the map represents the consolidated list of {@linkplain
- * ContextConfigurationAttributes context configuration attributes} for a
- * given level in the context hierarchy (potentially across the test class
- * hierarchy), keyed by the {@link ContextConfiguration#name() name} of the
- * context hierarchy level.
- *
- *
If a given level in the context hierarchy does not have an explicit
- * name (i.e., configured via {@link ContextConfiguration#name}), a name will
- * be generated for that hierarchy level by appending the numerical level to
- * the {@link #GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX}.
- *
- * @param testClass the class for which to resolve the context hierarchy map
- * (must not be {@code null})
- * @return a map of context configuration attributes for the context hierarchy,
- * keyed by context hierarchy level name; never {@code null}
- * @throws IllegalArgumentException if the lists of context configuration
- * attributes for each level in the {@code @ContextHierarchy} do not define
- * unique context configuration within the overall hierarchy.
- *
- * @since 3.2.2
- * @see #resolveContextHierarchyAttributes(Class)
- */
- static Map> buildContextHierarchyMap(Class> testClass) {
- final Map> map = new LinkedHashMap>();
- int hierarchyLevel = 1;
-
- for (List configAttributesList : resolveContextHierarchyAttributes(testClass)) {
- for (ContextConfigurationAttributes configAttributes : configAttributesList) {
- String name = configAttributes.getName();
-
- // Assign a generated name?
- if (!StringUtils.hasText(name)) {
- name = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + hierarchyLevel;
- }
-
- // Encountered a new context hierarchy level?
- if (!map.containsKey(name)) {
- hierarchyLevel++;
- map.put(name, new ArrayList());
- }
-
- map.get(name).add(configAttributes);
- }
- }
-
- // Check for uniqueness
- Set> set = new HashSet>(map.values());
- if (set.size() != map.size()) {
- String msg = String.format("The @ContextConfiguration elements configured via "
- + "@ContextHierarchy in test class [%s] and its superclasses must "
- + "define unique contexts per hierarchy level.", testClass.getName());
- logger.error(msg);
- throw new IllegalStateException(msg);
- }
-
- return map;
- }
-
- /**
- * Resolve the list of {@linkplain ContextConfigurationAttributes context
- * configuration attributes} for the supplied {@linkplain Class test class} and its
- * superclasses.
- *
- * Note that the {@link ContextConfiguration#inheritLocations inheritLocations} and
- * {@link ContextConfiguration#inheritInitializers() inheritInitializers} flags of
- * {@link ContextConfiguration @ContextConfiguration} will not
- * be taken into consideration. If these flags need to be honored, that must be
- * handled manually when traversing the list returned by this method.
- *
- * @param testClass the class for which to resolve the configuration attributes (must
- * not be {@code null})
- * @return the list of configuration attributes for the specified class, ordered
- * bottom-up (i.e., as if we were traversing up the class hierarchy);
- * never {@code null}
- * @throws IllegalArgumentException if the supplied class is {@code null} or if
- * {@code @ContextConfiguration} is not present on the supplied class
- */
- static List resolveContextConfigurationAttributes(Class> testClass) {
- Assert.notNull(testClass, "Class must not be null");
-
- final List attributesList = new ArrayList();
-
- Class annotationType = ContextConfiguration.class;
-
- AnnotationDescriptor descriptor = findAnnotationDescriptor(testClass, annotationType);
- Assert.notNull(descriptor, String.format(
- "Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]",
- annotationType.getName(), testClass.getName()));
-
- while (descriptor != null) {
- convertAnnotationAttributesToConfigAttributesAndAddToList(descriptor.getAnnotationAttributes(),
- descriptor.getRootDeclaringClass(), attributesList);
- descriptor = findAnnotationDescriptor(descriptor.getRootDeclaringClass().getSuperclass(), annotationType);
- }
-
- return attributesList;
- }
-
- /**
- * Resolve the list of merged {@code ApplicationContextInitializer} classes for the
- * supplied list of {@code ContextConfigurationAttributes}.
- *
- * Note that the {@link ContextConfiguration#inheritInitializers inheritInitializers}
- * flag of {@link ContextConfiguration @ContextConfiguration} will be taken into
- * consideration. Specifically, if the {@code inheritInitializers} flag is set to
- * {@code true} for a given level in the class hierarchy represented by the provided
- * configuration attributes, context initializer classes defined at the given level
- * will be merged with those defined in higher levels of the class hierarchy.
- *
- * @param configAttributesList the list of configuration attributes to process; must
- * not be {@code null} or empty; must be ordered bottom-up
- * (i.e., as if we were traversing up the class hierarchy)
- * @return the set of merged context initializer classes, including those from
- * superclasses if appropriate (never {@code null})
- * @since 3.2
- */
- static Set>> resolveInitializerClasses(
- List configAttributesList) {
- Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty");
-
- final Set>> initializerClasses = //
- new HashSet>>();
-
- for (ContextConfigurationAttributes configAttributes : configAttributesList) {
- if (logger.isTraceEnabled()) {
- logger.trace(String.format("Processing context initializers for context configuration attributes %s",
- configAttributes));
- }
-
- initializerClasses.addAll(Arrays.asList(configAttributes.getInitializers()));
-
- if (!configAttributes.isInheritInitializers()) {
- break;
- }
- }
-
- return initializerClasses;
- }
-
- /**
- * Resolve active bean definition profiles for the supplied {@link Class}.
- *
- * Note that the {@link ActiveProfiles#inheritProfiles inheritProfiles} flag of
- * {@link ActiveProfiles @ActiveProfiles} will be taken into consideration.
- * Specifically, if the {@code inheritProfiles} flag is set to {@code true}, profiles
- * defined in the test class will be merged with those defined in superclasses.
- *
- * @param testClass the class for which to resolve the active profiles (must not be
- * {@code null})
- * @return the set of active profiles for the specified class, including active
- * profiles from superclasses if appropriate (never {@code null})
- * @see ActiveProfiles
- * @see org.springframework.context.annotation.Profile
- */
- static String[] resolveActiveProfiles(Class> testClass) {
- Assert.notNull(testClass, "Class must not be null");
-
- Class annotationType = ActiveProfiles.class;
-
- AnnotationDescriptor descriptor = findAnnotationDescriptor(testClass, annotationType);
-
- if (descriptor == null && logger.isDebugEnabled()) {
- logger.debug(String.format(
- "Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]",
- annotationType.getName(), testClass.getName()));
- }
-
- final Set activeProfiles = new HashSet();
-
- while (descriptor != null) {
- Class> rootDeclaringClass = descriptor.getRootDeclaringClass();
- Class> declaringClass = descriptor.getDeclaringClass();
-
- AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes();
- if (logger.isTraceEnabled()) {
- logger.trace(String.format("Retrieved @ActiveProfiles attributes [%s] for declaring class [%s].",
- annAttrs, declaringClass.getName()));
- }
- validateActiveProfilesConfiguration(declaringClass, annAttrs);
-
- String[] profiles = annAttrs.getStringArray("profiles");
- String[] valueProfiles = annAttrs.getStringArray("value");
- Class extends ActiveProfilesResolver> resolverClass = annAttrs.getClass("resolver");
-
- boolean resolverDeclared = !ActiveProfilesResolver.class.equals(resolverClass);
- boolean valueDeclared = !ObjectUtils.isEmpty(valueProfiles);
-
- if (resolverDeclared) {
- ActiveProfilesResolver resolver = null;
- try {
- resolver = instantiateClass(resolverClass, ActiveProfilesResolver.class);
- }
- catch (Exception e) {
- String msg = String.format("Could not instantiate ActiveProfilesResolver of "
- + "type [%s] for test class [%s].", resolverClass.getName(), rootDeclaringClass.getName());
- logger.error(msg);
- throw new IllegalStateException(msg, e);
- }
-
- profiles = resolver.resolve(rootDeclaringClass);
- if (profiles == null) {
- String msg = String.format(
- "ActiveProfilesResolver [%s] returned a null array of bean definition profiles.",
- resolverClass.getName());
- logger.error(msg);
- throw new IllegalStateException(msg);
- }
- }
- else if (valueDeclared) {
- profiles = valueProfiles;
- }
-
- for (String profile : profiles) {
- if (StringUtils.hasText(profile)) {
- activeProfiles.add(profile.trim());
- }
- }
-
- descriptor = annAttrs.getBoolean("inheritProfiles") ? findAnnotationDescriptor(
- rootDeclaringClass.getSuperclass(), annotationType) : null;
- }
-
- return StringUtils.toStringArray(activeProfiles);
- }
-
- private static void validateActiveProfilesConfiguration(Class> declaringClass, AnnotationAttributes annAttrs) {
- String[] valueProfiles = annAttrs.getStringArray("value");
- String[] profiles = annAttrs.getStringArray("profiles");
- Class extends ActiveProfilesResolver> resolverClass = annAttrs.getClass("resolver");
- boolean valueDeclared = !ObjectUtils.isEmpty(valueProfiles);
- boolean profilesDeclared = !ObjectUtils.isEmpty(profiles);
- boolean resolverDeclared = !ActiveProfilesResolver.class.equals(resolverClass);
-
- String msg = null;
-
- if (valueDeclared && profilesDeclared) {
- msg = String.format("Test class [%s] has been configured with @ActiveProfiles' 'value' [%s] "
- + "and 'profiles' [%s] attributes. Only one declaration of active bean "
- + "definition profiles is permitted per @ActiveProfiles annotation.", declaringClass.getName(),
- ObjectUtils.nullSafeToString(valueProfiles), ObjectUtils.nullSafeToString(profiles));
- }
- else if (valueDeclared && resolverDeclared) {
- msg = String.format("Test class [%s] has been configured with @ActiveProfiles' 'value' [%s] "
- + "and 'resolver' [%s] attributes. Only one source of active bean "
- + "definition profiles is permitted per @ActiveProfiles annotation, "
- + "either declaritively or programmatically.", declaringClass.getName(),
- ObjectUtils.nullSafeToString(valueProfiles), resolverClass.getName());
- }
- else if (profilesDeclared && resolverDeclared) {
- msg = String.format("Test class [%s] has been configured with @ActiveProfiles' 'profiles' [%s] "
- + "and 'resolver' [%s] attributes. Only one source of active bean "
- + "definition profiles is permitted per @ActiveProfiles annotation, "
- + "either declaritively or programmatically.", declaringClass.getName(),
- ObjectUtils.nullSafeToString(profiles), resolverClass.getName());
- }
-
- if (msg != null) {
- logger.error(msg);
- throw new IllegalStateException(msg);
- }
- }
-
- /**
- * Build the {@link MergedContextConfiguration merged context configuration} for
- * the supplied {@link Class testClass} and {@code defaultContextLoaderClassName},
- * taking into account context hierarchies declared via
- * {@link ContextHierarchy @ContextHierarchy} and
- * {@link ContextConfiguration @ContextConfiguration}.
- * @param testClass the test class for which the {@code MergedContextConfiguration}
- * should be built (must not be {@code null})
- * @param defaultContextLoaderClassName the name of the default {@code ContextLoader}
- * class to use (may be {@code null})
- * @param cacheAwareContextLoaderDelegate the cache-aware context loader delegate to
- * be passed to the {@code MergedContextConfiguration} constructor
- * @return the merged context configuration
- * @see #buildContextHierarchyMap(Class)
- * @see #buildMergedContextConfiguration(Class, List, String, MergedContextConfiguration, CacheAwareContextLoaderDelegate)
- */
- @SuppressWarnings({ "unchecked" })
- static MergedContextConfiguration buildMergedContextConfiguration(Class> testClass,
- String defaultContextLoaderClassName, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
-
- if (findAnnotationDescriptorForTypes(testClass, ContextConfiguration.class, ContextHierarchy.class) == null) {
- if (logger.isInfoEnabled()) {
- logger.info(String.format(
- "Neither @ContextConfiguration nor @ContextHierarchy found for test class [%s]",
- testClass.getName()));
- }
- return new MergedContextConfiguration(testClass, null, null, null, null);
- }
-
- if (findAnnotation(testClass, ContextHierarchy.class) != null) {
- Map> hierarchyMap = buildContextHierarchyMap(testClass);
-
- MergedContextConfiguration parentConfig = null;
- MergedContextConfiguration mergedConfig = null;
-
- for (List list : hierarchyMap.values()) {
- List reversedList = new ArrayList(list);
- Collections.reverse(reversedList);
-
- // Don't use the supplied testClass; instead ensure that we are
- // building the MCC for the actual test class that declared the
- // configuration for the current level in the context hierarchy.
- Assert.notEmpty(reversedList, "ContextConfigurationAttributes list must not be empty");
- Class> declaringClass = reversedList.get(0).getDeclaringClass();
-
- mergedConfig = buildMergedContextConfiguration(declaringClass, reversedList,
- defaultContextLoaderClassName, parentConfig, cacheAwareContextLoaderDelegate);
- parentConfig = mergedConfig;
- }
-
- // Return the last level in the context hierarchy
- return mergedConfig;
- }
- else {
- return buildMergedContextConfiguration(testClass, resolveContextConfigurationAttributes(testClass),
- defaultContextLoaderClassName, null, cacheAwareContextLoaderDelegate);
- }
- }
-
- /**
- * Build the {@link MergedContextConfiguration merged context configuration} for the
- * supplied {@link Class testClass}, context configuration attributes,
- * {@code defaultContextLoaderClassName}, and parent context configuration.
- *
- * @param testClass the test class for which the {@code MergedContextConfiguration}
- * should be built (must not be {@code null})
- * @param configAttributesList the list of context configuration attributes for the
- * specified test class, ordered bottom-up (i.e., as if we were
- * traversing up the class hierarchy); never {@code null} or empty
- * @param defaultContextLoaderClassName the name of the default {@code ContextLoader}
- * class to use (may be {@code null})
- * @param parentConfig the merged context configuration for the parent application
- * context in a context hierarchy, or {@code null} if there is no parent
- * @param cacheAwareContextLoaderDelegate the cache-aware context loader delegate to
- * be passed to the {@code MergedContextConfiguration} constructor
- * @return the merged context configuration
- * @see #resolveContextLoader
- * @see #resolveContextConfigurationAttributes
- * @see SmartContextLoader#processContextConfiguration
- * @see ContextLoader#processLocations
- * @see #resolveActiveProfiles
- * @see MergedContextConfiguration
- */
- private static MergedContextConfiguration buildMergedContextConfiguration(final Class> testClass,
- final List configAttributesList,
- final String defaultContextLoaderClassName, MergedContextConfiguration parentConfig,
- CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
-
- final ContextLoader contextLoader = resolveContextLoader(testClass, configAttributesList,
- defaultContextLoaderClassName);
- final List locationsList = new ArrayList();
- final List> classesList = new ArrayList>();
-
- for (ContextConfigurationAttributes configAttributes : configAttributesList) {
- if (logger.isTraceEnabled()) {
- logger.trace(String.format("Processing locations and classes for context configuration attributes %s",
- configAttributes));
- }
-
- if (contextLoader instanceof SmartContextLoader) {
- SmartContextLoader smartContextLoader = (SmartContextLoader) contextLoader;
- smartContextLoader.processContextConfiguration(configAttributes);
- locationsList.addAll(0, Arrays.asList(configAttributes.getLocations()));
- classesList.addAll(0, Arrays.asList(configAttributes.getClasses()));
- }
- else {
- String[] processedLocations = contextLoader.processLocations(configAttributes.getDeclaringClass(),
- configAttributes.getLocations());
- locationsList.addAll(0, Arrays.asList(processedLocations));
- // Legacy ContextLoaders don't know how to process classes
- }
-
- if (!configAttributes.isInheritLocations()) {
- break;
- }
- }
-
- String[] locations = StringUtils.toStringArray(locationsList);
- Class>[] classes = ClassUtils.toClassArray(classesList);
- Set>> initializerClasses = resolveInitializerClasses(configAttributesList);
- String[] activeProfiles = resolveActiveProfiles(testClass);
-
- MergedContextConfiguration mergedConfig = buildWebMergedContextConfiguration(testClass, locations, classes,
- initializerClasses, activeProfiles, contextLoader, cacheAwareContextLoaderDelegate, parentConfig);
-
- if (mergedConfig == null) {
- mergedConfig = new MergedContextConfiguration(testClass, locations, classes, initializerClasses,
- activeProfiles, contextLoader, cacheAwareContextLoaderDelegate, parentConfig);
- }
-
- return mergedConfig;
- }
-
- /**
- * Load the {@link org.springframework.test.context.web.WebAppConfiguration}
- * class, using reflection in order to avoid package cycles.
- *
- * @return the {@code @WebAppConfiguration} class or {@code null} if it cannot be loaded
- * @since 3.2
- */
- @SuppressWarnings("unchecked")
- private static Class extends Annotation> loadWebAppConfigurationClass() {
- Class extends Annotation> webAppConfigClass = null;
- try {
- webAppConfigClass = (Class extends Annotation>) ClassUtils.forName(WEB_APP_CONFIGURATION_CLASS_NAME,
- ContextLoaderUtils.class.getClassLoader());
- }
- catch (Throwable t) {
- if (logger.isDebugEnabled()) {
- logger.debug("Could not load @WebAppConfiguration class [" + WEB_APP_CONFIGURATION_CLASS_NAME + "].", t);
- }
- }
- return webAppConfigClass;
- }
-
- /**
- * Attempt to build a {@link org.springframework.test.context.web.WebMergedContextConfiguration}
- * from the supplied arguments, using reflection in order to avoid package cycles.
- *
- * @return the {@code WebMergedContextConfiguration} or {@code null} if it could not be built
- * @since 3.2
- */
- @SuppressWarnings("unchecked")
- private static MergedContextConfiguration buildWebMergedContextConfiguration(
- Class> testClass,
- String[] locations,
- Class>[] classes,
- Set>> initializerClasses,
- String[] activeProfiles, ContextLoader contextLoader,
- CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parentConfig) {
-
- Class extends Annotation> webAppConfigClass = loadWebAppConfigurationClass();
- if (webAppConfigClass != null) {
-
- Annotation annotation = findAnnotation(testClass, webAppConfigClass);
- if (annotation != null) {
-
- String resourceBasePath = (String) AnnotationUtils.getValue(annotation);
-
- try {
- Class extends MergedContextConfiguration> webMergedConfigClass = (Class extends MergedContextConfiguration>) ClassUtils.forName(
- WEB_MERGED_CONTEXT_CONFIGURATION_CLASS_NAME, ContextLoaderUtils.class.getClassLoader());
-
- Constructor extends MergedContextConfiguration> constructor = ClassUtils.getConstructorIfAvailable(
- webMergedConfigClass, Class.class, String[].class, Class[].class, Set.class, String[].class,
- String.class, ContextLoader.class, CacheAwareContextLoaderDelegate.class,
- MergedContextConfiguration.class);
-
- if (constructor != null) {
- return instantiateClass(constructor, testClass, locations, classes, initializerClasses,
- activeProfiles, resourceBasePath, contextLoader, cacheAwareContextLoaderDelegate,
- parentConfig);
- }
- }
- catch (Throwable t) {
- if (logger.isDebugEnabled()) {
- logger.debug("Could not instantiate [" + WEB_MERGED_CONTEXT_CONFIGURATION_CLASS_NAME + "].", t);
- }
- }
- }
- }
-
- return null;
- }
-
-}
diff --git a/spring-test/src/main/java/org/springframework/test/context/DefaultBootstrapContext.java b/spring-test/src/main/java/org/springframework/test/context/DefaultBootstrapContext.java
new file mode 100644
index 00000000000..c0f43571584
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/DefaultBootstrapContext.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2014 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.core.style.ToStringCreator;
+import org.springframework.util.Assert;
+
+/**
+ * Default implementation of the {@link BootstrapContext} interface.
+ *
+ * @author Sam Brannen
+ * @since 4.1
+ */
+class DefaultBootstrapContext implements BootstrapContext {
+
+ private final Class> testClass;
+ private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate;
+
+
+ DefaultBootstrapContext(Class> testClass, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
+ Assert.notNull(testClass, "Test class must not be null");
+ Assert.notNull(cacheAwareContextLoaderDelegate, "CacheAwareContextLoaderDelegate must not be null");
+ this.testClass = testClass;
+ this.cacheAwareContextLoaderDelegate = cacheAwareContextLoaderDelegate;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Class> getTestClass() {
+ return this.testClass;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public CacheAwareContextLoaderDelegate getCacheAwareContextLoaderDelegate() {
+ return this.cacheAwareContextLoaderDelegate;
+ }
+
+ /**
+ * Provide a String representation of this bootstrap context's state.
+ */
+ @Override
+ public String toString() {
+ return new ToStringCreator(this)//
+ .append("testClass", testClass)//
+ .append("cacheAwareContextLoaderDelegate", nullSafeToString(cacheAwareContextLoaderDelegate))//
+ .toString();
+ }
+
+ /**
+ * Generate a null-safe {@link String} representation of the supplied
+ * {@link CacheAwareContextLoaderDelegate} based solely on the fully qualified
+ * name of the delegate or "null" if the supplied delegate is
+ * {@code null}.
+ */
+ private static String nullSafeToString(CacheAwareContextLoaderDelegate delegate) {
+ return delegate == null ? "null" : delegate.getClass().getName();
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/DefaultCacheAwareContextLoaderDelegate.java b/spring-test/src/main/java/org/springframework/test/context/DefaultCacheAwareContextLoaderDelegate.java
new file mode 100644
index 00000000000..2d1c2e0d9a6
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/DefaultCacheAwareContextLoaderDelegate.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2002-2014 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.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
+import org.springframework.util.Assert;
+
+/**
+ * Default implementation of the {@link CacheAwareContextLoaderDelegate} interface.
+ *
+ * Although {@code DefaultCacheAwareContextLoaderDelegate} was first introduced
+ * in Spring Framework 4.1, the initial implementation of this class was extracted
+ * from the existing code base for {@code CacheAwareContextLoaderDelegate} when
+ * {@code CacheAwareContextLoaderDelegate} was converted into an interface.
+ *
+ * @author Sam Brannen
+ * @since 4.1
+ */
+class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContextLoaderDelegate {
+
+ private static final Log logger = LogFactory.getLog(DefaultCacheAwareContextLoaderDelegate.class);
+
+ private final ContextCache contextCache;
+
+
+ DefaultCacheAwareContextLoaderDelegate(ContextCache contextCache) {
+ Assert.notNull(contextCache, "ContextCache must not be null");
+ this.contextCache = contextCache;
+ }
+
+ /**
+ * Load the {@code ApplicationContext} for the supplied merged context configuration.
+ *
Supports both the {@link SmartContextLoader} and {@link ContextLoader} SPIs.
+ * @throws Exception if an error occurs while loading the application context
+ */
+ private ApplicationContext loadContextInternal(MergedContextConfiguration mergedContextConfiguration)
+ throws Exception {
+ ContextLoader contextLoader = mergedContextConfiguration.getContextLoader();
+ Assert.notNull(contextLoader, "Cannot load an ApplicationContext with a NULL 'contextLoader'. "
+ + "Consider annotating your test class with @ContextConfiguration or @ContextHierarchy.");
+
+ ApplicationContext applicationContext;
+
+ if (contextLoader instanceof SmartContextLoader) {
+ SmartContextLoader smartContextLoader = (SmartContextLoader) contextLoader;
+ applicationContext = smartContextLoader.loadContext(mergedContextConfiguration);
+ }
+ else {
+ String[] locations = mergedContextConfiguration.getLocations();
+ Assert.notNull(locations, "Cannot load an ApplicationContext with a NULL 'locations' array. "
+ + "Consider annotating your test class with @ContextConfiguration or @ContextHierarchy.");
+ applicationContext = contextLoader.loadContext(locations);
+ }
+
+ return applicationContext;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration) {
+ synchronized (contextCache) {
+ ApplicationContext context = contextCache.get(mergedContextConfiguration);
+ if (context == null) {
+ try {
+ context = loadContextInternal(mergedContextConfiguration);
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Storing ApplicationContext in cache under key [%s].",
+ mergedContextConfiguration));
+ }
+ contextCache.put(mergedContextConfiguration, context);
+ }
+ catch (Exception ex) {
+ throw new IllegalStateException("Failed to load ApplicationContext", ex);
+ }
+ }
+ else {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Retrieved ApplicationContext from cache with key [%s].",
+ mergedContextConfiguration));
+ }
+ }
+ return context;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void closeContext(MergedContextConfiguration mergedContextConfiguration, HierarchyMode hierarchyMode) {
+ contextCache.remove(mergedContextConfiguration, hierarchyMode);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/DefaultTestContext.java b/spring-test/src/main/java/org/springframework/test/context/DefaultTestContext.java
index 22d6580a3df..b3fcfc13826 100644
--- a/spring-test/src/main/java/org/springframework/test/context/DefaultTestContext.java
+++ b/spring-test/src/main/java/org/springframework/test/context/DefaultTestContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2014 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.
@@ -40,8 +40,6 @@ class DefaultTestContext extends AttributeAccessorSupport implements TestContext
private static final long serialVersionUID = -5827157174866681233L;
- private final ContextCache contextCache;
-
private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate;
private final MergedContextConfiguration mergedContextConfiguration;
@@ -56,49 +54,31 @@ class DefaultTestContext extends AttributeAccessorSupport implements TestContext
/**
- * Delegates to {@link #DefaultTestContext(Class, ContextCache, String)} with a
- * value of {@code null} for the default {@code ContextLoader} class name.
+ * Construct a new test context using the supplied {@link TestContextBootstrapper}.
+ * @param testContextBootstrapper the {@code TestContextBootstrapper} to use
+ * to construct the test context (must not be {@code null})
*/
- DefaultTestContext(Class> testClass, ContextCache contextCache) {
- this(testClass, contextCache, null);
+ DefaultTestContext(TestContextBootstrapper testContextBootstrapper) {
+ Assert.notNull(testContextBootstrapper, "TestContextBootstrapper must not be null");
+
+ BootstrapContext bootstrapContext = testContextBootstrapper.getBootstrapContext();
+ this.testClass = bootstrapContext.getTestClass();
+ this.cacheAwareContextLoaderDelegate = bootstrapContext.getCacheAwareContextLoaderDelegate();
+ this.mergedContextConfiguration = testContextBootstrapper.buildMergedContextConfiguration();
}
/**
- * Construct a new test context for the supplied {@linkplain Class test class}
- * and {@linkplain ContextCache context cache} and parse the corresponding
- * {@link ContextConfiguration @ContextConfiguration} or
- * {@link ContextHierarchy @ContextHierarchy} annotation, if present.
- *
If the supplied class name for the default {@code ContextLoader}
- * is {@code null} or empty and no concrete {@code ContextLoader}
- * class is explicitly supplied via {@code @ContextConfiguration}, a
- * {@link org.springframework.test.context.support.DelegatingSmartContextLoader
- * DelegatingSmartContextLoader} or
- * {@link org.springframework.test.context.web.WebDelegatingSmartContextLoader
- * WebDelegatingSmartContextLoader} will be used instead.
- * @param testClass the test class for which the test context should be
- * constructed (must not be {@code null})
- * @param contextCache the context cache from which the constructed test
- * context should retrieve application contexts (must not be
- * {@code null})
- * @param defaultContextLoaderClassName the name of the default
- * {@code ContextLoader} class to use (may be {@code null})
+ * {@inheritDoc}
*/
- DefaultTestContext(Class> testClass, ContextCache contextCache, String defaultContextLoaderClassName) {
- Assert.notNull(testClass, "Test class must not be null");
- Assert.notNull(contextCache, "ContextCache must not be null");
-
- this.testClass = testClass;
- this.contextCache = contextCache;
- this.cacheAwareContextLoaderDelegate = new CacheAwareContextLoaderDelegate(contextCache);
- this.mergedContextConfiguration = ContextLoaderUtils.buildMergedContextConfiguration(testClass,
- defaultContextLoaderClassName, cacheAwareContextLoaderDelegate);
+ public ApplicationContext getApplicationContext() {
+ return cacheAwareContextLoaderDelegate.loadContext(mergedContextConfiguration);
}
/**
* {@inheritDoc}
*/
- public ApplicationContext getApplicationContext() {
- return cacheAwareContextLoaderDelegate.loadContext(mergedContextConfiguration);
+ public void markApplicationContextDirty(HierarchyMode hierarchyMode) {
+ cacheAwareContextLoaderDelegate.closeContext(mergedContextConfiguration, hierarchyMode);
}
/**
@@ -129,13 +109,6 @@ class DefaultTestContext extends AttributeAccessorSupport implements TestContext
return testException;
}
- /**
- * {@inheritDoc}
- */
- public void markApplicationContextDirty(HierarchyMode hierarchyMode) {
- contextCache.remove(mergedContextConfiguration, hierarchyMode);
- }
-
/**
* {@inheritDoc}
*/
diff --git a/spring-test/src/main/java/org/springframework/test/context/TestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/TestContextBootstrapper.java
new file mode 100644
index 00000000000..5e22e497e2a
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/TestContextBootstrapper.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2002-2014 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;
+
+/**
+ * {@code TestContextBootstrapper} defines a strategy SPI for bootstrapping the
+ * Spring TestContext Framework.
+ *
+ *
A custom bootstrapping strategy can be configured for a test class via
+ * {@link BootstrapWith @BootstrapWith}, either directly or as a meta-annotation.
+ * See {@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration}
+ * for an example.
+ *
+ *
The {@link TestContextManager} uses a {@code TestContextBootstrapper} to
+ * {@linkplain #getTestExecutionListeners get the TestExecutionListeners} for the
+ * current test and to {@linkplain #buildMergedContextConfiguration build the
+ * merged context configuration} necessary to create the {@link TestContext} that
+ * it manages.
+ *
+ *
Concrete implementations must provide a {@code public} no-args constructor.
+ *
+ *
Note: this SPI might potentially change in the future in
+ * order to accommodate new requirements. Implementers are therefore strongly encouraged
+ * not to implement this interface directly but rather to extend
+ * {@link org.springframework.test.context.support.AbstractTestContextBootstrapper
+ * AbstractTestContextBootstrapper} or one of its concrete subclasses instead.
+ *
+ * @author Sam Brannen
+ * @since 4.1
+ * @see BootstrapWith
+ * @see BootstrapContext
+ */
+public interface TestContextBootstrapper {
+
+ /**
+ * Set the {@link BootstrapContext} to be used by this bootstrapper.
+ */
+ void setBootstrapContext(BootstrapContext bootstrapContext);
+
+ /**
+ * Get the {@link BootstrapContext} associated with this bootstrapper.
+ */
+ BootstrapContext getBootstrapContext();
+
+ /**
+ * Get a list of newly instantiated {@link TestExecutionListener TestExecutionListeners}
+ * for the test class in the {@link BootstrapContext} associated with this bootstrapper.
+ *
If {@link TestExecutionListeners @TestExecutionListeners} is not
+ * present on the test class in the {@code BootstrapContext},
+ * default listeners should be returned. Concrete implementations
+ * are free to determine what comprises the set of default listeners.
+ *
The {@link TestExecutionListeners#inheritListeners() inheritListeners}
+ * flag of {@link TestExecutionListeners @TestExecutionListeners} must be
+ * taken into consideration. Specifically, if the {@code inheritListeners}
+ * flag is set to {@code true}, listeners declared for a given test class must
+ * be appended to the end of the list of listeners declared in superclasses.
+ * @return a list of {@code TestExecutionListener} instances
+ */
+ List getTestExecutionListeners();
+
+ /**
+ * Build the {@linkplain MergedContextConfiguration merged context configuration}
+ * for the test class in the {@link BootstrapContext} associated with this
+ * bootstrapper.
+ * Implementations must take the following into account when building the
+ * merged configuration:
+ *
+ * - Context hierarchies declared via {@link ContextHierarchy @ContextHierarchy}
+ * and {@link ContextConfiguration @ContextConfiguration}
+ * - Active bean definition profiles declared via {@link ActiveProfiles @ActiveProfiles}
+ * - {@linkplain org.springframework.context.ApplicationContextInitializer
+ * Context initializers} declared via {@link ContextConfiguration#initializers}
+ * Consult the Javadoc for the aforementioned annotations for details on
+ * the required semantics.
+ *
When determining which {@link ContextLoader} to use for a given test
+ * class, the following algorithm should be used:
+ *
+ * - If a {@code ContextLoader} class has been explicitly declared via
+ * {@link ContextConfiguration#loader}, use it.
+ * - Otherwise, if the name of a {@linkplain BootstrapContext#getCustomDefaultContextLoaderClassName
+ * custom default ContextLoader class} has been provided in the {@link BootstrapContext},
+ * use it.
+ * - Otherwise, concrete implementations are free to determine which
+ * {@code ContextLoader} class to use as as default.
+ * @return the merged context configuration, never {@code null}
+ */
+ MergedContextConfiguration buildMergedContextConfiguration();
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java b/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java
index 8255db3b27e..75e22ab16c9 100644
--- a/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java
+++ b/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2014 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.
@@ -18,119 +18,111 @@ package org.springframework.test.context;
import java.lang.reflect.Method;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
-import java.util.LinkedHashSet;
import java.util.List;
-import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.springframework.beans.BeanUtils;
-import org.springframework.context.ApplicationContext;
-import org.springframework.core.annotation.AnnotationAttributes;
-import org.springframework.test.context.MetaAnnotationUtils.AnnotationDescriptor;
import org.springframework.util.Assert;
-import org.springframework.util.ObjectUtils;
-
-import static org.springframework.test.context.MetaAnnotationUtils.*;
/**
- *
- * {@code TestContextManager} is the main entry point into the
- * Spring TestContext Framework, which provides support for loading and
- * accessing {@link ApplicationContext application contexts}, dependency
- * injection of test instances,
- * {@link org.springframework.transaction.annotation.Transactional
- * transactional} execution of test methods, etc.
- *
- *
- * Specifically, a {@code TestContextManager} is responsible for managing a
+ * {@code TestContextManager} is the main entry point into the Spring
+ * TestContext Framework, which provides support for loading and accessing
+ * {@link org.springframework.context.ApplicationContext application contexts},
+ * dependency injection of test instances,
+ * {@link org.springframework.transaction.annotation.Transactional transactional}
+ * execution of test methods, etc.
+ *
+ *
Specifically, a {@code TestContextManager} is responsible for managing a
* single {@link TestContext} and signaling events to all registered
* {@link TestExecutionListener TestExecutionListeners} at well defined test
* execution points:
- *
+ *
*
* - {@link #beforeTestClass() before test class execution}: prior to any
* before class methods of a particular testing framework (e.g., JUnit
- * 4's {@link org.junit.BeforeClass @BeforeClass})
+ * 4's {@link org.junit.BeforeClass @BeforeClass})
* - {@link #prepareTestInstance(Object) test instance preparation}:
* immediately following instantiation of the test instance
* - {@link #beforeTestMethod(Object, Method) before test method execution}:
* prior to any before methods of a particular testing framework (e.g.,
- * JUnit 4's {@link org.junit.Before @Before})
+ * JUnit 4's {@link org.junit.Before @Before})
* - {@link #afterTestMethod(Object, Method, Throwable) after test method
* execution}: after any after methods of a particular testing
- * framework (e.g., JUnit 4's {@link org.junit.After @After})
+ * framework (e.g., JUnit 4's {@link org.junit.After @After})
* - {@link #afterTestClass() after test class execution}: after any
* after class methods of a particular testing framework (e.g., JUnit
- * 4's {@link org.junit.AfterClass @AfterClass})
+ * 4's {@link org.junit.AfterClass @AfterClass})
*
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 2.5
+ * @see BootstrapWith
+ * @see BootstrapContext
+ * @see TestContextBootstrapper
* @see TestContext
+ * @see TestExecutionListener
* @see TestExecutionListeners
* @see ContextConfiguration
+ * @see ContextHierarchy
* @see org.springframework.test.context.transaction.TransactionConfiguration
*/
public class TestContextManager {
- private static final String[] DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES = new String[] {
- "org.springframework.test.context.web.ServletTestExecutionListener",
- "org.springframework.test.context.support.DependencyInjectionTestExecutionListener",
- "org.springframework.test.context.support.DirtiesContextTestExecutionListener",
- "org.springframework.test.context.transaction.TransactionalTestExecutionListener" };
-
private static final Log logger = LogFactory.getLog(TestContextManager.class);
/**
- * Cache of Spring application contexts. This needs to be static, as tests
- * may be destroyed and recreated between running individual test methods,
- * for example with JUnit.
+ * Cache of Spring application contexts.
+ * This needs to be static, since test instances may be destroyed and
+ * recreated between invocations of individual test methods, as is the case
+ * with JUnit.
*/
static final ContextCache contextCache = new ContextCache();
private final TestContext testContext;
+ private final TestContextBootstrapper testContextBootstrapper;
+
private final List testExecutionListeners = new ArrayList();
/**
- * Delegates to {@link #TestContextManager(Class, String)} with a value of
- * {@code null} for the default {@code ContextLoader} class name.
+ * Construct a new {@code TestContextManager} for the specified {@linkplain Class
+ * test class} and automatically {@link #registerTestExecutionListeners register} the
+ * {@link TestExecutionListener TestExecutionListeners} configured for the test class
+ * via the {@link TestExecutionListeners @TestExecutionListeners} annotation.
+ * @param testClass the test class to be managed
+ * @see #registerTestExecutionListeners(List)
*/
public TestContextManager(Class> testClass) {
- this(testClass, null);
+ CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = new DefaultCacheAwareContextLoaderDelegate(
+ contextCache);
+ BootstrapContext bootstrapContext = new DefaultBootstrapContext(testClass, cacheAwareContextLoaderDelegate);
+ this.testContextBootstrapper = BootstrapUtils.resolveTestContextBootstrapper(bootstrapContext);
+ this.testContext = new DefaultTestContext(testContextBootstrapper);
+ registerTestExecutionListeners(testContextBootstrapper.getTestExecutionListeners());
}
/**
- * Constructs a new {@code TestContextManager} for the specified {@linkplain Class
- * test class} and automatically {@link #registerTestExecutionListeners registers} the
- * {@link TestExecutionListener TestExecutionListeners} configured for the test class
- * via the {@link TestExecutionListeners @TestExecutionListeners} annotation.
- * @param testClass the test class to be managed
- * @param defaultContextLoaderClassName the name of the default {@code ContextLoader}
- * class to use (may be {@code null})
- * @see #registerTestExecutionListeners(TestExecutionListener...)
+ * Get the {@link TestContext} managed by this {@code TestContextManager}.
*/
- public TestContextManager(Class> testClass, String defaultContextLoaderClassName) {
- this.testContext = new DefaultTestContext(testClass, contextCache, defaultContextLoaderClassName);
- registerTestExecutionListeners(retrieveTestExecutionListeners(testClass));
+ protected final TestContext getTestContext() {
+ return this.testContext;
}
/**
- * Returns the {@link TestContext} managed by this
- * {@code TestContextManager}.
+ * Register the supplied list of {@link TestExecutionListener TestExecutionListeners}
+ * by appending them to the list of listeners used by this {@code TestContextManager}.
+ * @see #registerTestExecutionListeners(TestExecutionListener...)
*/
- protected final TestContext getTestContext() {
- return this.testContext;
+ public void registerTestExecutionListeners(List testExecutionListeners) {
+ registerTestExecutionListeners(testExecutionListeners.toArray(new TestExecutionListener[testExecutionListeners.size()]));
}
/**
- * Register the supplied {@link TestExecutionListener TestExecutionListeners}
- * by appending them to the set of listeners used by this {@code TestContextManager}.
+ * Register the supplied array of {@link TestExecutionListener TestExecutionListeners}
+ * by appending them to the list of listeners used by this {@code TestContextManager}.
*/
public void registerTestExecutionListeners(TestExecutionListener... testExecutionListeners) {
for (TestExecutionListener listener : testExecutionListeners) {
@@ -162,110 +154,11 @@ public class TestContextManager {
return listenersReversed;
}
- /**
- * Retrieve an array of newly instantiated {@link TestExecutionListener TestExecutionListeners}
- * for the specified {@link Class class}. If {@link TestExecutionListeners @TestExecutionListeners}
- * is not present on the supplied class, the default listeners will be returned.
- * Note that the {@link TestExecutionListeners#inheritListeners() inheritListeners} flag of
- * {@link TestExecutionListeners @TestExecutionListeners} will be taken into consideration.
- * Specifically, if the {@code inheritListeners} flag is set to {@code true}, listeners
- * defined in the annotated class will be appended to the listeners defined in superclasses.
- * @param clazz the test class for which the listeners should be retrieved
- * @return an array of TestExecutionListeners for the specified class
- */
- @SuppressWarnings("unchecked")
- private TestExecutionListener[] retrieveTestExecutionListeners(Class> clazz) {
- Assert.notNull(clazz, "Class must not be null");
- Class annotationType = TestExecutionListeners.class;
- List> classesList = new ArrayList>();
-
- AnnotationDescriptor descriptor = findAnnotationDescriptor(clazz, annotationType);
-
- // Use defaults?
- if (descriptor == null) {
- if (logger.isDebugEnabled()) {
- logger.debug("@TestExecutionListeners is not present for class [" + clazz + "]: using defaults.");
- }
- classesList.addAll(getDefaultTestExecutionListenerClasses());
- }
- else {
- // Traverse the class hierarchy...
- while (descriptor != null) {
- Class> declaringClass = descriptor.getDeclaringClass();
-
- AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes();
- if (logger.isTraceEnabled()) {
- logger.trace(String.format(
- "Retrieved @TestExecutionListeners attributes [%s] for declaring class [%s].", annAttrs,
- declaringClass));
- }
-
- Class extends TestExecutionListener>[] valueListenerClasses = (Class extends TestExecutionListener>[]) annAttrs.getClassArray("value");
- Class extends TestExecutionListener>[] listenerClasses = (Class extends TestExecutionListener>[]) annAttrs.getClassArray("listeners");
- if (!ObjectUtils.isEmpty(valueListenerClasses) && !ObjectUtils.isEmpty(listenerClasses)) {
- String msg = String.format(
- "Class [%s] has been configured with @TestExecutionListeners' 'value' [%s] "
- + "and 'listeners' [%s] attributes. Use one or the other, but not both.",
- declaringClass, ObjectUtils.nullSafeToString(valueListenerClasses),
- ObjectUtils.nullSafeToString(listenerClasses));
- logger.error(msg);
- throw new IllegalStateException(msg);
- }
- else if (!ObjectUtils.isEmpty(valueListenerClasses)) {
- listenerClasses = valueListenerClasses;
- }
-
- if (listenerClasses != null) {
- classesList.addAll(0, Arrays.> asList(listenerClasses));
- }
-
- descriptor = (annAttrs.getBoolean("inheritListeners") ? findAnnotationDescriptor(
- descriptor.getRootDeclaringClass().getSuperclass(), annotationType) : null);
- }
- }
-
- List listeners = new ArrayList(classesList.size());
- for (Class extends TestExecutionListener> listenerClass : classesList) {
- try {
- listeners.add(BeanUtils.instantiateClass(listenerClass));
- }
- catch (NoClassDefFoundError err) {
- if (logger.isInfoEnabled()) {
- logger.info(String.format("Could not instantiate TestExecutionListener class [%s]. "
- + "Specify custom listener classes or make the default listener classes "
- + "(and their dependencies) available.", listenerClass.getName()));
- }
- }
- }
- return listeners.toArray(new TestExecutionListener[listeners.size()]);
- }
-
- /**
- * Determine the default {@link TestExecutionListener} classes.
- */
- @SuppressWarnings("unchecked")
- protected Set> getDefaultTestExecutionListenerClasses() {
- Set> defaultListenerClasses = new LinkedHashSet>();
- for (String className : DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES) {
- try {
- defaultListenerClasses.add((Class extends TestExecutionListener>) getClass().getClassLoader().loadClass(
- className));
- }
- catch (Throwable t) {
- if (logger.isDebugEnabled()) {
- logger.debug("Could not load default TestExecutionListener class [" + className
- + "]. Specify custom listener classes or make the default listener classes available.", t);
- }
- }
- }
- return defaultListenerClasses;
- }
-
/**
* Hook for pre-processing a test class before execution of any
* tests within the class. Should be called prior to any framework-specific
* before class methods (e.g., methods annotated with JUnit's
- * {@link org.junit.BeforeClass @BeforeClass}).
+ * {@link org.junit.BeforeClass @BeforeClass}).
* An attempt will be made to give each registered
* {@link TestExecutionListener} a chance to pre-process the test class
* execution. If a listener throws an exception, however, the remaining
@@ -331,7 +224,7 @@ public class TestContextManager {
* {@link Method test method}, for example for setting up test fixtures,
* starting a transaction, etc. Should be called prior to any
* framework-specific before methods (e.g., methods annotated with
- * JUnit's {@link org.junit.Before @Before}).
+ * JUnit's {@link org.junit.Before @Before}).
*
The managed {@link TestContext} will be updated with the supplied
* {@code testInstance} and {@code testMethod}.
*
An attempt will be made to give each registered
@@ -369,7 +262,7 @@ public class TestContextManager {
* {@link Method test method}, for example for tearing down test fixtures,
* ending a transaction, etc. Should be called after any framework-specific
* after methods (e.g., methods annotated with JUnit's
- * {@link org.junit.After @After}).
+ * {@link org.junit.After @After}).
*
The managed {@link TestContext} will be updated with the supplied
* {@code testInstance}, {@code testMethod}, and
* {@code exception}.
@@ -421,7 +314,7 @@ public class TestContextManager {
* Hook for post-processing a test class after execution of all
* tests within the class. Should be called after any framework-specific
* after class methods (e.g., methods annotated with JUnit's
- * {@link org.junit.AfterClass @AfterClass}).
+ * {@link org.junit.AfterClass @AfterClass}).
*
Each registered {@link TestExecutionListener} will be given a chance to
* post-process the test class. If a listener throws an exception, the
* remaining registered listeners will still be called, but the first
diff --git a/spring-test/src/main/java/org/springframework/test/context/TestExecutionListeners.java b/spring-test/src/main/java/org/springframework/test/context/TestExecutionListeners.java
index 30d8f43d30f..74464aa8d80 100644
--- a/spring-test/src/main/java/org/springframework/test/context/TestExecutionListeners.java
+++ b/spring-test/src/main/java/org/springframework/test/context/TestExecutionListeners.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2014 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.
@@ -46,10 +46,21 @@ import java.lang.annotation.Target;
@Target(ElementType.TYPE)
public @interface TestExecutionListeners {
+ /**
+ * Alias for {@link #listeners() listeners}.
+ *
+ *
This attribute may not be used in conjunction with
+ * {@link #listeners}, but it may be used instead of {@link #listeners}.
+ */
+ Class extends TestExecutionListener>[] value() default {};
+
/**
* The {@link TestExecutionListener TestExecutionListeners} to register with
* a {@link TestContextManager}.
*
+ *
This attribute may not be used in conjunction with
+ * {@link #value}, but it may be used instead of {@link #value}.
+ *
* @see org.springframework.test.context.web.ServletTestExecutionListener
* @see org.springframework.test.context.support.DependencyInjectionTestExecutionListener
* @see org.springframework.test.context.support.DirtiesContextTestExecutionListener
@@ -57,11 +68,6 @@ public @interface TestExecutionListeners {
*/
Class extends TestExecutionListener>[] listeners() default {};
- /**
- * Alias for {@link #listeners() listeners}.
- */
- Class extends TestExecutionListener>[] value() default {};
-
/**
* Whether or not {@link #value() TestExecutionListeners} from superclasses
* should be inherited.
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java b/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java
index 4ec6101e422..d8633716c4f 100644
--- a/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java
+++ b/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java
@@ -50,36 +50,32 @@ import org.springframework.test.context.junit4.statements.SpringRepeat;
import org.springframework.util.ReflectionUtils;
/**
- *
- * {@code SpringJUnit4ClassRunner} is a custom extension of
+ *
{@code SpringJUnit4ClassRunner} is a custom extension of JUnit's
* {@link BlockJUnit4ClassRunner} which provides functionality of the
* Spring TestContext Framework to standard JUnit 4.5+ tests by means
* of the {@link TestContextManager} and associated support classes and
* annotations.
- *
- *
- * The following list constitutes all annotations currently supported directly
+ *
+ *
The following list constitutes all annotations currently supported directly
* by {@code SpringJUnit4ClassRunner}.
- * (Note that additional annotations may be supported by various
- * {@link org.springframework.test.context.TestExecutionListener
- * TestExecutionListeners})
- *
+ * (Note that additional annotations may be supported by various {@link
+ * org.springframework.test.context.TestExecutionListener TestExecutionListeners}
+ * or {@link org.springframework.test.context.TestContextBootstrapper
+ * TestContextBootstrapper} implementations)
+ *
*
* - {@link Test#expected() @Test(expected=...)}
* - {@link Test#timeout() @Test(timeout=...)}
* - {@link Timed @Timed}
* - {@link Repeat @Repeat}
* - {@link Ignore @Ignore}
- * -
- * {@link org.springframework.test.annotation.ProfileValueSourceConfiguration
+ *
- {@link org.springframework.test.annotation.ProfileValueSourceConfiguration
* @ProfileValueSourceConfiguration}
- * - {@link org.springframework.test.annotation.IfProfileValue
- * @IfProfileValue}
+ * - {@link org.springframework.test.annotation.IfProfileValue @IfProfileValue}
*
- *
- * NOTE: As of Spring 3.0, {@code SpringJUnit4ClassRunner} requires
- * JUnit 4.5+.
- *
+ *
+ * NOTE: As of Spring 3.0, {@code SpringJUnit4ClassRunner}
+ * requires JUnit 4.5 or higher.
*
* @author Sam Brannen
* @author Juergen Hoeller
@@ -110,14 +106,12 @@ public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner {
}
/**
- * Creates a new {@link TestContextManager} for the supplied test class and
- * the configured default {@code ContextLoader} class name.
- * Can be overridden by subclasses.
+ * Creates a new {@link TestContextManager} for the supplied test class.
+ *
Can be overridden by subclasses.
* @param clazz the test class to be managed
- * @see #getDefaultContextLoaderClassName(Class)
*/
protected TestContextManager createTestContextManager(Class> clazz) {
- return new TestContextManager(clazz, getDefaultContextLoaderClassName(clazz));
+ return new TestContextManager(clazz);
}
/**
@@ -127,21 +121,6 @@ public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner {
return this.testContextManager;
}
- /**
- * Get the name of the default {@code ContextLoader} class to use for
- * the supplied test class. The named class will be used if the test class
- * does not explicitly declare a {@code ContextLoader} class via the
- * {@code @ContextConfiguration} annotation.
- *
The default implementation returns {@code null}, thus implying use
- * of the standard default {@code ContextLoader} class name.
- * Can be overridden by subclasses.
- * @param clazz the test class
- * @return {@code null}
- */
- protected String getDefaultContextLoaderClassName(Class> clazz) {
- return null;
- }
-
/**
* Returns a description suitable for an ignored test class if the test is
* disabled via {@code @IfProfileValue} at the class-level, and
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
new file mode 100644
index 00000000000..0a6f472fec6
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright 2002-2014 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.support;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.BeanUtils;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.annotation.AnnotationUtils;
+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.ContextHierarchy;
+import org.springframework.test.context.ContextLoader;
+import org.springframework.test.context.MergedContextConfiguration;
+import org.springframework.test.context.SmartContextLoader;
+import org.springframework.test.context.TestContextBootstrapper;
+import org.springframework.test.context.TestExecutionListener;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.util.MetaAnnotationUtils;
+import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * Abstract implementation of the {@link TestContextBootstrapper} interface which
+ * provides most of the behavior required by a bootstrapper.
+ *
+ *
Concrete subclasses typically will only need to provide implementations for
+ * the following {@code abstract} methods:
+ *
+ * - {@link #getDefaultTestExecutionListenerClassNames()}
+ *
- {@link #getDefaultContextLoaderClass(Class)}
+ *
- {@link #buildMergedContextConfiguration(Class, String[], Class[], Set, String[], ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)}
+ *
+ * @author Sam Brannen
+ * @since 4.1
+ */
+public abstract class AbstractTestContextBootstrapper implements TestContextBootstrapper {
+
+ private static final Log logger = LogFactory.getLog(AbstractTestContextBootstrapper.class);
+
+ private BootstrapContext bootstrapContext;
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setBootstrapContext(BootstrapContext bootstrapContext) {
+ this.bootstrapContext = bootstrapContext;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public BootstrapContext getBootstrapContext() {
+ return this.bootstrapContext;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public final List getTestExecutionListeners() {
+ Class> clazz = getBootstrapContext().getTestClass();
+ Class annotationType = TestExecutionListeners.class;
+ List> classesList = new ArrayList>();
+
+ AnnotationDescriptor descriptor = MetaAnnotationUtils.findAnnotationDescriptor(clazz,
+ annotationType);
+
+ // Use defaults?
+ if (descriptor == null) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("@TestExecutionListeners is not present for class [" + clazz + "]: using defaults.");
+ }
+ classesList.addAll(getDefaultTestExecutionListenerClasses());
+ }
+ else {
+ // Traverse the class hierarchy...
+ while (descriptor != null) {
+ Class> declaringClass = descriptor.getDeclaringClass();
+
+ AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes();
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format(
+ "Retrieved @TestExecutionListeners attributes [%s] for declaring class [%s].", annAttrs,
+ declaringClass));
+ }
+
+ Class extends TestExecutionListener>[] valueListenerClasses = (Class extends TestExecutionListener>[]) annAttrs.getClassArray("value");
+ Class extends TestExecutionListener>[] listenerClasses = (Class extends TestExecutionListener>[]) annAttrs.getClassArray("listeners");
+ if (!ObjectUtils.isEmpty(valueListenerClasses) && !ObjectUtils.isEmpty(listenerClasses)) {
+ String msg = String.format(
+ "Class [%s] has been configured with @TestExecutionListeners' 'value' [%s] "
+ + "and 'listeners' [%s] attributes. Use one or the other, but not both.",
+ declaringClass, ObjectUtils.nullSafeToString(valueListenerClasses),
+ ObjectUtils.nullSafeToString(listenerClasses));
+ logger.error(msg);
+ throw new IllegalStateException(msg);
+ }
+ else if (!ObjectUtils.isEmpty(valueListenerClasses)) {
+ listenerClasses = valueListenerClasses;
+ }
+
+ if (listenerClasses != null) {
+ classesList.addAll(0, Arrays.> asList(listenerClasses));
+ }
+
+ descriptor = (annAttrs.getBoolean("inheritListeners") ? MetaAnnotationUtils.findAnnotationDescriptor(
+ descriptor.getRootDeclaringClass().getSuperclass(), annotationType) : null);
+ }
+ }
+
+ List listeners = new ArrayList(classesList.size());
+ for (Class extends TestExecutionListener> listenerClass : classesList) {
+ try {
+ listeners.add(BeanUtils.instantiateClass(listenerClass));
+ }
+ catch (NoClassDefFoundError err) {
+ if (logger.isInfoEnabled()) {
+ logger.info(String.format("Could not instantiate TestExecutionListener [%s]. "
+ + "Specify custom listener classes or make the default listener classes "
+ + "(and their dependencies) available.", listenerClass.getName()));
+ }
+ }
+ }
+ return listeners;
+ }
+
+ /**
+ * Get the default {@link TestExecutionListener} classes for this bootstrapper.
+ *
This method is invoked by {@link #getTestExecutionListeners()} and
+ * delegates to {@link #getDefaultTestExecutionListenerClassNames()} to
+ * retrieve the class names.
+ *
If a particular class cannot be loaded, a {@code DEBUG} message will
+ * be logged, but the associated exception will not be rethrown.
+ */
+ @SuppressWarnings("unchecked")
+ protected Set> getDefaultTestExecutionListenerClasses() {
+ Set> defaultListenerClasses = new LinkedHashSet>();
+ for (String className : getDefaultTestExecutionListenerClassNames()) {
+ try {
+ defaultListenerClasses.add((Class extends TestExecutionListener>) getClass().getClassLoader().loadClass(
+ className));
+ }
+ catch (Throwable t) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Could not load default TestExecutionListener class [" + className
+ + "]. Specify custom listener classes or make the default listener classes available.", t);
+ }
+ }
+ }
+ return defaultListenerClasses;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @SuppressWarnings({ "unchecked" })
+ @Override
+ public final MergedContextConfiguration buildMergedContextConfiguration() {
+ Class> testClass = getBootstrapContext().getTestClass();
+ CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = getBootstrapContext().getCacheAwareContextLoaderDelegate();
+
+ if (MetaAnnotationUtils.findAnnotationDescriptorForTypes(testClass, ContextConfiguration.class,
+ ContextHierarchy.class) == null) {
+ if (logger.isInfoEnabled()) {
+ logger.info(String.format(
+ "Neither @ContextConfiguration nor @ContextHierarchy found for test class [%s]",
+ testClass.getName()));
+ }
+ return new MergedContextConfiguration(testClass, null, null, null, null);
+ }
+
+ if (AnnotationUtils.findAnnotation(testClass, ContextHierarchy.class) != null) {
+ Map> hierarchyMap = ContextLoaderUtils.buildContextHierarchyMap(testClass);
+
+ MergedContextConfiguration parentConfig = null;
+ MergedContextConfiguration mergedConfig = null;
+
+ for (List list : hierarchyMap.values()) {
+ List reversedList = new ArrayList(list);
+ Collections.reverse(reversedList);
+
+ // Don't use the supplied testClass; instead ensure that we are
+ // building the MCC for the actual test class that declared the
+ // configuration for the current level in the context hierarchy.
+ Assert.notEmpty(reversedList, "ContextConfigurationAttributes list must not be empty");
+ Class> declaringClass = reversedList.get(0).getDeclaringClass();
+
+ mergedConfig = buildMergedContextConfiguration(declaringClass, reversedList, parentConfig,
+ cacheAwareContextLoaderDelegate);
+ parentConfig = mergedConfig;
+ }
+
+ // Return the last level in the context hierarchy
+ return mergedConfig;
+ }
+ else {
+ return buildMergedContextConfiguration(testClass,
+ ContextLoaderUtils.resolveContextConfigurationAttributes(testClass), null,
+ cacheAwareContextLoaderDelegate);
+ }
+ }
+
+ /**
+ * Build the {@link MergedContextConfiguration merged context configuration}
+ * for the supplied {@link Class testClass}, context configuration attributes,
+ * and parent context configuration.
+ *
+ * @param testClass the test class for which the {@code MergedContextConfiguration}
+ * should be built (must not be {@code null})
+ * @param configAttributesList the list of context configuration attributes for the
+ * specified test class, ordered bottom-up (i.e., as if we were
+ * traversing up the class hierarchy); never {@code null} or empty
+ * @param parentConfig the merged context configuration for the parent application
+ * context in a context hierarchy, or {@code null} if there is no parent
+ * @param cacheAwareContextLoaderDelegate the cache-aware context loader delegate to
+ * be passed to the {@code MergedContextConfiguration} constructor
+ * @return the merged context configuration
+ * @see #resolveContextLoader
+ * @see ContextLoaderUtils#resolveContextConfigurationAttributes
+ * @see SmartContextLoader#processContextConfiguration
+ * @see ContextLoader#processLocations
+ * @see ActiveProfilesUtils#resolveActiveProfiles
+ * @see ApplicationContextInitializerUtils#resolveInitializerClasses
+ * @see MergedContextConfiguration
+ */
+ private MergedContextConfiguration buildMergedContextConfiguration(final Class> testClass,
+ final List configAttributesList, MergedContextConfiguration parentConfig,
+ CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
+
+ final ContextLoader contextLoader = resolveContextLoader(testClass, configAttributesList);
+ final List locationsList = new ArrayList();
+ final List> classesList = new ArrayList>();
+
+ for (ContextConfigurationAttributes configAttributes : configAttributesList) {
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("Processing locations and classes for context configuration attributes %s",
+ configAttributes));
+ }
+
+ if (contextLoader instanceof SmartContextLoader) {
+ SmartContextLoader smartContextLoader = (SmartContextLoader) contextLoader;
+ smartContextLoader.processContextConfiguration(configAttributes);
+ locationsList.addAll(0, Arrays.asList(configAttributes.getLocations()));
+ classesList.addAll(0, Arrays.asList(configAttributes.getClasses()));
+ }
+ else {
+ String[] processedLocations = contextLoader.processLocations(configAttributes.getDeclaringClass(),
+ configAttributes.getLocations());
+ locationsList.addAll(0, Arrays.asList(processedLocations));
+ // Legacy ContextLoaders don't know how to process classes
+ }
+
+ if (!configAttributes.isInheritLocations()) {
+ break;
+ }
+ }
+
+ String[] locations = StringUtils.toStringArray(locationsList);
+ Class>[] classes = ClassUtils.toClassArray(classesList);
+ Set>> initializerClasses = //
+ ApplicationContextInitializerUtils.resolveInitializerClasses(configAttributesList);
+ String[] activeProfiles = ActiveProfilesUtils.resolveActiveProfiles(testClass);
+
+ return buildMergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles,
+ contextLoader, cacheAwareContextLoaderDelegate, parentConfig);
+ }
+
+ /**
+ * Resolve the {@link ContextLoader} {@linkplain Class class} to use for the
+ * supplied list of {@link ContextConfigurationAttributes} and then instantiate
+ * and return that {@code ContextLoader}.
+ *
+ * If the user has not explicitly declared which loader to use, the value
+ * returned from {@link #getDefaultContextLoaderClass} will be used as the
+ * default context loader class. For details on the class resolution process,
+ * see {@link #resolveExplicitContextLoaderClass} and
+ * {@link #getDefaultContextLoaderClass}.
+ *
+ * @param testClass the test class for which the {@code ContextLoader} should be
+ * resolved; must not be {@code null}
+ * @param configAttributesList the list of configuration attributes to process; must
+ * not be {@code null} or empty; must be ordered bottom-up
+ * (i.e., as if we were traversing up the class hierarchy)
+ * @return the resolved {@code ContextLoader} for the supplied {@code testClass}
+ * (never {@code null})
+ */
+ private ContextLoader resolveContextLoader(Class> testClass,
+ List configAttributesList) {
+ Assert.notNull(testClass, "Class must not be null");
+ Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty");
+
+ Class extends ContextLoader> contextLoaderClass = resolveExplicitContextLoaderClass(configAttributesList);
+ if (contextLoaderClass == null) {
+ contextLoaderClass = getDefaultContextLoaderClass(testClass);
+ }
+
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("Using ContextLoader class [%s] for test class [%s]",
+ contextLoaderClass.getName(), testClass.getName()));
+ }
+
+ return BeanUtils.instantiateClass(contextLoaderClass, ContextLoader.class);
+ }
+
+ /**
+ * Resolve the {@link ContextLoader} {@linkplain Class class} to use for the supplied
+ * list of {@link ContextConfigurationAttributes}.
+ *
+ * Beginning with the first level in the context configuration attributes hierarchy:
+ *
+ *
+ * - If the {@link ContextConfigurationAttributes#getContextLoaderClass()
+ * contextLoaderClass} property of {@link ContextConfigurationAttributes} is
+ * configured with an explicit class, that class will be returned.
+ * - If an explicit {@code ContextLoader} class is not specified at the current
+ * level in the hierarchy, traverse to the next level in the hierarchy and return to
+ * step #1.
+ *
+ *
+ * @param configAttributesList the list of configuration attributes to process;
+ * must not be {@code null} or empty; must be ordered bottom-up
+ * (i.e., as if we were traversing up the class hierarchy)
+ * @return the {@code ContextLoader} class to use for the supplied configuration
+ * attributes, or {@code null} if no explicit loader is found
+ * @throws IllegalArgumentException if supplied configuration attributes are
+ * {@code null} or empty
+ */
+ private Class extends ContextLoader> resolveExplicitContextLoaderClass(
+ List configAttributesList) {
+ Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty");
+
+ for (ContextConfigurationAttributes configAttributes : configAttributesList) {
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("Resolving ContextLoader for context configuration attributes %s",
+ configAttributes));
+ }
+
+ Class extends ContextLoader> contextLoaderClass = configAttributes.getContextLoaderClass();
+ if (!ContextLoader.class.equals(contextLoaderClass)) {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format(
+ "Found explicit ContextLoader class [%s] for context configuration attributes %s",
+ contextLoaderClass.getName(), configAttributes));
+ }
+ return contextLoaderClass;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Get the names of the default {@link TestExecutionListener} classes for
+ * this bootstrapper.
+ * This method is invoked by {@link #getDefaultTestExecutionListenerClasses()}.
+ * @return an unmodifiable list of names of default {@code
+ * TestExecutionListener} classes
+ */
+ protected abstract List getDefaultTestExecutionListenerClassNames();
+
+ /**
+ * Determine the default {@link ContextLoader} class to use for the supplied
+ * test class.
+ * The class returned by this method will only be used if a {@code ContextLoader}
+ * class has not been explicitly declared via {@link ContextConfiguration#loader}.
+ * @param testClass the test class for which to retrieve the default
+ * {@code ContextLoader} class
+ */
+ protected abstract Class extends ContextLoader> getDefaultContextLoaderClass(Class> testClass);
+
+ /**
+ * Build a {@link MergedContextConfiguration} instance from the supplied,
+ * merged values.
+ *
+ *
Concrete subclasses typically will only need to instantiate
+ * {@link MergedContextConfiguration} (or a specialized subclass thereof)
+ * from the provided values; further processing and merging of values is likely
+ * unnecessary.
+ *
+ * @param testClass the test class for which the {@code MergedContextConfiguration}
+ * should be built (must not be {@code null})
+ * @param locations the merged resource locations
+ * @param classes the merged annotated classes
+ * @param initializerClasses the merged context initializer classes
+ * @param activeProfiles the merged active bean definition profiles
+ * @param contextLoader the resolved {@code ContextLoader}
+ * @param cacheAwareContextLoaderDelegate the cache-aware context loader delegate to
+ * be provided to the instantiated {@code MergedContextConfiguration}
+ * @param parentConfig the merged context configuration for the parent application
+ * context in a context hierarchy, or {@code null} if there is no parent
+ * @return the fully initialized {@code MergedContextConfiguration}
+ */
+ protected abstract MergedContextConfiguration buildMergedContextConfiguration(
+ Class> testClass,
+ String[] locations,
+ Class>[] classes,
+ Set>> initializerClasses,
+ String[] activeProfiles, ContextLoader contextLoader,
+ CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parentConfig);
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/ActiveProfilesUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/ActiveProfilesUtils.java
new file mode 100644
index 00000000000..22b8cfdb5d8
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/support/ActiveProfilesUtils.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2002-2014 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.support;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.BeanUtils;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ActiveProfilesResolver;
+import org.springframework.test.util.MetaAnnotationUtils;
+import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * Utility methods for working with {@link ActiveProfiles @ActiveProfiles} and
+ * {@link ActiveProfilesResolver ActiveProfilesResolvers}.
+ *
+ * Although {@code ActiveProfilesUtils} was first introduced in Spring Framework
+ * 4.1, the initial implementations of methods in this class were based on the
+ * existing code base in {@code ContextLoaderUtils}.
+ *
+ * @author Sam Brannen
+ * @author Michail Nikolaev
+ * @since 4.1
+ * @see ActiveProfiles
+ * @see ActiveProfilesResolver
+ */
+abstract class ActiveProfilesUtils {
+
+ private static final Log logger = LogFactory.getLog(ActiveProfilesUtils.class);
+
+
+ private ActiveProfilesUtils() {
+ /* no-op */
+ }
+
+ /**
+ * Resolve active bean definition profiles for the supplied {@link Class}.
+ *
+ *
Note that the {@link ActiveProfiles#inheritProfiles inheritProfiles} flag of
+ * {@link ActiveProfiles @ActiveProfiles} will be taken into consideration.
+ * Specifically, if the {@code inheritProfiles} flag is set to {@code true}, profiles
+ * defined in the test class will be merged with those defined in superclasses.
+ *
+ * @param testClass the class for which to resolve the active profiles (must not be
+ * {@code null})
+ * @return the set of active profiles for the specified class, including active
+ * profiles from superclasses if appropriate (never {@code null})
+ * @see ActiveProfiles
+ * @see ActiveProfilesResolver
+ * @see org.springframework.context.annotation.Profile
+ */
+ static String[] resolveActiveProfiles(Class> testClass) {
+ Assert.notNull(testClass, "Class must not be null");
+
+ final Set activeProfiles = new HashSet();
+
+ Class annotationType = ActiveProfiles.class;
+ AnnotationDescriptor descriptor = MetaAnnotationUtils.findAnnotationDescriptor(testClass,
+ annotationType);
+ if (descriptor == null && logger.isDebugEnabled()) {
+ logger.debug(String.format(
+ "Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]",
+ annotationType.getName(), testClass.getName()));
+ }
+
+ while (descriptor != null) {
+ Class> rootDeclaringClass = descriptor.getRootDeclaringClass();
+ Class> declaringClass = descriptor.getDeclaringClass();
+
+ AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes();
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("Retrieved @ActiveProfiles attributes [%s] for declaring class [%s].",
+ annAttrs, declaringClass.getName()));
+ }
+ validateActiveProfilesConfiguration(declaringClass, annAttrs);
+
+ Class extends ActiveProfilesResolver> resolverClass = annAttrs.getClass("resolver");
+ if (ActiveProfilesResolver.class.equals(resolverClass)) {
+ resolverClass = DefaultActiveProfilesResolver.class;
+ }
+
+ ActiveProfilesResolver resolver = null;
+ try {
+ resolver = BeanUtils.instantiateClass(resolverClass, ActiveProfilesResolver.class);
+ }
+ catch (Exception e) {
+ String msg = String.format("Could not instantiate ActiveProfilesResolver of "
+ + "type [%s] for test class [%s].", resolverClass.getName(), rootDeclaringClass.getName());
+ logger.error(msg);
+ throw new IllegalStateException(msg, e);
+ }
+
+ String[] profiles = resolver.resolve(rootDeclaringClass);
+ if (profiles == null) {
+ String msg = String.format(
+ "ActiveProfilesResolver [%s] returned a null array of bean definition profiles.",
+ resolverClass.getName());
+ logger.error(msg);
+ throw new IllegalStateException(msg);
+ }
+
+ for (String profile : profiles) {
+ if (StringUtils.hasText(profile)) {
+ activeProfiles.add(profile.trim());
+ }
+ }
+
+ descriptor = annAttrs.getBoolean("inheritProfiles") ? MetaAnnotationUtils.findAnnotationDescriptor(
+ rootDeclaringClass.getSuperclass(), annotationType) : null;
+ }
+
+ return StringUtils.toStringArray(activeProfiles);
+ }
+
+ private static void validateActiveProfilesConfiguration(Class> declaringClass, AnnotationAttributes annAttrs) {
+ String[] valueProfiles = annAttrs.getStringArray("value");
+ String[] profiles = annAttrs.getStringArray("profiles");
+ boolean valueDeclared = !ObjectUtils.isEmpty(valueProfiles);
+ boolean profilesDeclared = !ObjectUtils.isEmpty(profiles);
+
+ if (valueDeclared && profilesDeclared) {
+ String msg = String.format("Class [%s] has been configured with @ActiveProfiles' 'value' [%s] "
+ + "and 'profiles' [%s] attributes. Only one declaration of active bean "
+ + "definition profiles is permitted per @ActiveProfiles annotation.", declaringClass.getName(),
+ ObjectUtils.nullSafeToString(valueProfiles), ObjectUtils.nullSafeToString(profiles));
+ logger.error(msg);
+ throw new IllegalStateException(msg);
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/ApplicationContextInitializerUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/ApplicationContextInitializerUtils.java
new file mode 100644
index 00000000000..f9b196b4e8d
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/support/ApplicationContextInitializerUtils.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2002-2014 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.support;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextConfigurationAttributes;
+import org.springframework.util.Assert;
+
+/**
+ * Utility methods for working with {@link ApplicationContextInitializer
+ * ApplicationContextInitializers}.
+ *
+ * Although {@code ApplicationContextInitializerUtils} was first introduced
+ * in Spring Framework 4.1, the initial implementations of methods in this class
+ * were based on the existing code base in {@code ContextLoaderUtils}.
+ *
+ * @author Sam Brannen
+ * @since 4.1
+ * @see ContextConfiguration#initializers
+ */
+abstract class ApplicationContextInitializerUtils {
+
+ private static final Log logger = LogFactory.getLog(ApplicationContextInitializerUtils.class);
+
+
+ private ApplicationContextInitializerUtils() {
+ /* no-op */
+ }
+
+ /**
+ * Resolve the set of merged {@code ApplicationContextInitializer} classes for the
+ * supplied list of {@code ContextConfigurationAttributes}.
+ *
+ *
Note that the {@link ContextConfiguration#inheritInitializers inheritInitializers}
+ * flag of {@link ContextConfiguration @ContextConfiguration} will be taken into
+ * consideration. Specifically, if the {@code inheritInitializers} flag is set to
+ * {@code true} for a given level in the class hierarchy represented by the provided
+ * configuration attributes, context initializer classes defined at the given level
+ * will be merged with those defined in higher levels of the class hierarchy.
+ *
+ * @param configAttributesList the list of configuration attributes to process; must
+ * not be {@code null} or empty; must be ordered bottom-up
+ * (i.e., as if we were traversing up the class hierarchy)
+ * @return the set of merged context initializer classes, including those from
+ * superclasses if appropriate (never {@code null})
+ * @since 3.2
+ */
+ static Set>> resolveInitializerClasses(
+ List configAttributesList) {
+ Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty");
+
+ final Set>> initializerClasses = //
+ new HashSet>>();
+
+ for (ContextConfigurationAttributes configAttributes : configAttributesList) {
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("Processing context initializers for context configuration attributes %s",
+ configAttributes));
+ }
+
+ initializerClasses.addAll(Arrays.asList(configAttributes.getInitializers()));
+
+ if (!configAttributes.isInheritInitializers()) {
+ break;
+ }
+ }
+
+ return initializerClasses;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java
new file mode 100644
index 00000000000..edca68e314c
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright 2002-2014 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.support;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextConfigurationAttributes;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.ContextLoader;
+import org.springframework.test.context.SmartContextLoader;
+import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
+import org.springframework.test.util.MetaAnnotationUtils.UntypedAnnotationDescriptor;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+import static org.springframework.core.annotation.AnnotationUtils.*;
+import static org.springframework.test.util.MetaAnnotationUtils.*;
+
+/**
+ * Utility methods for working with {@link ContextLoader ContextLoaders} and
+ * {@link SmartContextLoader SmartContextLoaders} and resolving resource locations,
+ * annotated classes, and application context initializers.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ * @see ContextLoader
+ * @see SmartContextLoader
+ * @see ContextConfiguration
+ * @see ContextConfigurationAttributes
+ * @see ApplicationContextInitializer
+ * @see ContextHierarchy
+ */
+abstract class ContextLoaderUtils {
+
+ static final String GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX = "ContextHierarchyLevel#";
+
+ private static final Log logger = LogFactory.getLog(ContextLoaderUtils.class);
+
+
+ private ContextLoaderUtils() {
+ /* no-op */
+ }
+
+ /**
+ * Convenience method for creating a {@link ContextConfigurationAttributes}
+ * instance from the supplied {@link ContextConfiguration} annotation and
+ * declaring class and then adding the attributes to the supplied list.
+ */
+ private static void convertContextConfigToConfigAttributesAndAddToList(ContextConfiguration contextConfiguration,
+ Class> declaringClass, final List attributesList) {
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("Retrieved @ContextConfiguration [%s] for declaring class [%s].",
+ contextConfiguration, declaringClass.getName()));
+ }
+
+ ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(declaringClass,
+ contextConfiguration);
+ if (logger.isTraceEnabled()) {
+ logger.trace("Resolved context configuration attributes: " + attributes);
+ }
+ attributesList.add(attributes);
+ }
+
+ /**
+ * Convenience method for creating a {@link ContextConfigurationAttributes}
+ * instance from the supplied {@link AnnotationAttributes} and declaring
+ * class and then adding the attributes to the supplied list.
+ * @since 4.0
+ */
+ private static void convertAnnotationAttributesToConfigAttributesAndAddToList(AnnotationAttributes annAttrs,
+ Class> declaringClass, final List attributesList) {
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("Retrieved @ContextConfiguration attributes [%s] for declaring class [%s].",
+ annAttrs, declaringClass.getName()));
+ }
+
+ ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(declaringClass, annAttrs);
+ if (logger.isTraceEnabled()) {
+ logger.trace("Resolved context configuration attributes: " + attributes);
+ }
+ attributesList.add(attributes);
+ }
+
+ /**
+ * Resolve the list of lists of {@linkplain ContextConfigurationAttributes context
+ * configuration attributes} for the supplied {@linkplain Class test class} and its
+ * superclasses, taking into account context hierarchies declared via
+ * {@link ContextHierarchy @ContextHierarchy} and
+ * {@link ContextConfiguration @ContextConfiguration}.
+ *
+ * The outer list represents a top-down ordering of context configuration
+ * attributes, where each element in the list represents the context configuration
+ * declared on a given test class in the class hierarchy. Each nested list
+ * contains the context configuration attributes declared either via a single
+ * instance of {@code @ContextConfiguration} on the particular class or via
+ * multiple instances of {@code @ContextConfiguration} declared within a
+ * single {@code @ContextHierarchy} instance on the particular class.
+ * Furthermore, each nested list maintains the order in which
+ * {@code @ContextConfiguration} instances are declared.
+ *
+ *
Note that the {@link ContextConfiguration#inheritLocations inheritLocations} and
+ * {@link ContextConfiguration#inheritInitializers() inheritInitializers} flags of
+ * {@link ContextConfiguration @ContextConfiguration} will not
+ * be taken into consideration. If these flags need to be honored, that must be
+ * handled manually when traversing the nested lists returned by this method.
+ *
+ * @param testClass the class for which to resolve the context hierarchy attributes
+ * (must not be {@code null})
+ * @return the list of lists of configuration attributes for the specified class;
+ * never {@code null}
+ * @throws IllegalArgumentException if the supplied class is {@code null}; if
+ * neither {@code @ContextConfiguration} nor {@code @ContextHierarchy} is
+ * present on the supplied class; or if a test class or composed annotation
+ * in the class hierarchy declares both {@code @ContextConfiguration} and
+ * {@code @ContextHierarchy} as top-level annotations.
+ * @throws IllegalStateException if no class in the class hierarchy declares
+ * {@code @ContextHierarchy}.
+ *
+ * @since 3.2.2
+ * @see #buildContextHierarchyMap(Class)
+ * @see #resolveContextConfigurationAttributes(Class)
+ */
+ @SuppressWarnings("unchecked")
+ static List> resolveContextHierarchyAttributes(Class> testClass) {
+ Assert.notNull(testClass, "Class must not be null");
+ Assert.state(findAnnotation(testClass, ContextHierarchy.class) != null, "@ContextHierarchy must be present");
+
+ final Class contextConfigType = ContextConfiguration.class;
+ final Class contextHierarchyType = ContextHierarchy.class;
+ final List> hierarchyAttributes = new ArrayList>();
+
+ UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(testClass, contextConfigType,
+ contextHierarchyType);
+ Assert.notNull(descriptor, String.format(
+ "Could not find an 'annotation declaring class' for annotation type [%s] or [%s] and test class [%s]",
+ contextConfigType.getName(), contextHierarchyType.getName(), testClass.getName()));
+
+ while (descriptor != null) {
+ Class> rootDeclaringClass = descriptor.getRootDeclaringClass();
+ Class> declaringClass = descriptor.getDeclaringClass();
+
+ boolean contextConfigDeclaredLocally = isAnnotationDeclaredLocally(contextConfigType, declaringClass);
+ boolean contextHierarchyDeclaredLocally = isAnnotationDeclaredLocally(contextHierarchyType, declaringClass);
+
+ if (contextConfigDeclaredLocally && contextHierarchyDeclaredLocally) {
+ String msg = String.format("Class [%s] has been configured with both @ContextConfiguration "
+ + "and @ContextHierarchy. Only one of these annotations may be declared on a test class "
+ + "or composed annotation.", declaringClass.getName());
+ logger.error(msg);
+ throw new IllegalStateException(msg);
+ }
+
+ final List configAttributesList = new ArrayList();
+
+ if (contextConfigDeclaredLocally) {
+ convertAnnotationAttributesToConfigAttributesAndAddToList(descriptor.getAnnotationAttributes(),
+ rootDeclaringClass, configAttributesList);
+ }
+ else if (contextHierarchyDeclaredLocally) {
+ ContextHierarchy contextHierarchy = getAnnotation(declaringClass, contextHierarchyType);
+ for (ContextConfiguration contextConfiguration : contextHierarchy.value()) {
+ convertContextConfigToConfigAttributesAndAddToList(contextConfiguration, rootDeclaringClass,
+ configAttributesList);
+ }
+ }
+ else {
+ // This should theoretically never happen...
+ String msg = String.format("Test class [%s] has been configured with neither @ContextConfiguration "
+ + "nor @ContextHierarchy as a class-level annotation.", rootDeclaringClass.getName());
+ logger.error(msg);
+ throw new IllegalStateException(msg);
+ }
+
+ hierarchyAttributes.add(0, configAttributesList);
+
+ descriptor = findAnnotationDescriptorForTypes(rootDeclaringClass.getSuperclass(), contextConfigType,
+ contextHierarchyType);
+ }
+
+ return hierarchyAttributes;
+ }
+
+ /**
+ * Build a context hierarchy map for the supplied {@linkplain Class
+ * test class} and its superclasses, taking into account context hierarchies
+ * declared via {@link ContextHierarchy @ContextHierarchy} and
+ * {@link ContextConfiguration @ContextConfiguration}.
+ *
+ * Each value in the map represents the consolidated list of {@linkplain
+ * ContextConfigurationAttributes context configuration attributes} for a
+ * given level in the context hierarchy (potentially across the test class
+ * hierarchy), keyed by the {@link ContextConfiguration#name() name} of the
+ * context hierarchy level.
+ *
+ *
If a given level in the context hierarchy does not have an explicit
+ * name (i.e., configured via {@link ContextConfiguration#name}), a name will
+ * be generated for that hierarchy level by appending the numerical level to
+ * the {@link #GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX}.
+ *
+ * @param testClass the class for which to resolve the context hierarchy map
+ * (must not be {@code null})
+ * @return a map of context configuration attributes for the context hierarchy,
+ * keyed by context hierarchy level name; never {@code null}
+ * @throws IllegalArgumentException if the lists of context configuration
+ * attributes for each level in the {@code @ContextHierarchy} do not define
+ * unique context configuration within the overall hierarchy.
+ *
+ * @since 3.2.2
+ * @see #resolveContextHierarchyAttributes(Class)
+ */
+ static Map> buildContextHierarchyMap(Class> testClass) {
+ final Map> map = new LinkedHashMap>();
+ int hierarchyLevel = 1;
+
+ for (List configAttributesList : resolveContextHierarchyAttributes(testClass)) {
+ for (ContextConfigurationAttributes configAttributes : configAttributesList) {
+ String name = configAttributes.getName();
+
+ // Assign a generated name?
+ if (!StringUtils.hasText(name)) {
+ name = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + hierarchyLevel;
+ }
+
+ // Encountered a new context hierarchy level?
+ if (!map.containsKey(name)) {
+ hierarchyLevel++;
+ map.put(name, new ArrayList());
+ }
+
+ map.get(name).add(configAttributes);
+ }
+ }
+
+ // Check for uniqueness
+ Set> set = new HashSet>(map.values());
+ if (set.size() != map.size()) {
+ String msg = String.format("The @ContextConfiguration elements configured via "
+ + "@ContextHierarchy in test class [%s] and its superclasses must "
+ + "define unique contexts per hierarchy level.", testClass.getName());
+ logger.error(msg);
+ throw new IllegalStateException(msg);
+ }
+
+ return map;
+ }
+
+ /**
+ * Resolve the list of {@linkplain ContextConfigurationAttributes context
+ * configuration attributes} for the supplied {@linkplain Class test class} and its
+ * superclasses.
+ *
+ * Note that the {@link ContextConfiguration#inheritLocations inheritLocations} and
+ * {@link ContextConfiguration#inheritInitializers() inheritInitializers} flags of
+ * {@link ContextConfiguration @ContextConfiguration} will not
+ * be taken into consideration. If these flags need to be honored, that must be
+ * handled manually when traversing the list returned by this method.
+ *
+ * @param testClass the class for which to resolve the configuration attributes (must
+ * not be {@code null})
+ * @return the list of configuration attributes for the specified class, ordered
+ * bottom-up (i.e., as if we were traversing up the class hierarchy);
+ * never {@code null}
+ * @throws IllegalArgumentException if the supplied class is {@code null} or if
+ * {@code @ContextConfiguration} is not present on the supplied class
+ */
+ static List resolveContextConfigurationAttributes(Class> testClass) {
+ Assert.notNull(testClass, "Class must not be null");
+
+ final List attributesList = new ArrayList();
+
+ Class annotationType = ContextConfiguration.class;
+
+ AnnotationDescriptor descriptor = findAnnotationDescriptor(testClass, annotationType);
+ Assert.notNull(descriptor, String.format(
+ "Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]",
+ annotationType.getName(), testClass.getName()));
+
+ while (descriptor != null) {
+ convertAnnotationAttributesToConfigAttributesAndAddToList(descriptor.getAnnotationAttributes(),
+ descriptor.getRootDeclaringClass(), attributesList);
+ descriptor = findAnnotationDescriptor(descriptor.getRootDeclaringClass().getSuperclass(), annotationType);
+ }
+
+ return attributesList;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/DefaultActiveProfilesResolver.java b/spring-test/src/main/java/org/springframework/test/context/support/DefaultActiveProfilesResolver.java
new file mode 100644
index 00000000000..46b1cb46a4f
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/support/DefaultActiveProfilesResolver.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2002-2014 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.support;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ActiveProfilesResolver;
+import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+
+import static org.springframework.test.util.MetaAnnotationUtils.*;
+
+/**
+ * Default implementation of the {@link ActiveProfilesResolver} strategy that
+ * resolves active bean definition profiles based solely on profiles
+ * configured declaratively via {@link ActiveProfiles#profiles} or
+ * {@link ActiveProfiles#value}.
+ *
+ * @author Sam Brannen
+ * @since 4.1
+ * @see ActiveProfiles
+ * @see ActiveProfilesResolver
+ */
+public class DefaultActiveProfilesResolver implements ActiveProfilesResolver {
+
+ private static final Log logger = LogFactory.getLog(DefaultActiveProfilesResolver.class);
+
+
+ /**
+ * Resolve the bean definition profiles for the given {@linkplain
+ * Class test class} based on profiles configured declaratively via
+ * {@link ActiveProfiles#profiles} or {@link ActiveProfiles#value}.
+ * @param testClass the test class for which the profiles should be resolved;
+ * never {@code null}
+ * @return the list of bean definition profiles to use when loading the
+ * {@code ApplicationContext}; never {@code null}
+ */
+ @Override
+ public String[] resolve(Class> testClass) {
+ Assert.notNull(testClass, "Class must not be null");
+
+ final Set activeProfiles = new HashSet();
+
+ Class annotationType = ActiveProfiles.class;
+ AnnotationDescriptor descriptor = findAnnotationDescriptor(testClass, annotationType);
+
+ if (descriptor == null) {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format(
+ "Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]",
+ annotationType.getName(), testClass.getName()));
+ }
+ }
+ else {
+ Class> rootDeclaringClass = descriptor.getRootDeclaringClass();
+ Class> declaringClass = descriptor.getDeclaringClass();
+
+ AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes();
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("Retrieved @ActiveProfiles attributes [%s] for declaring class [%s].",
+ annAttrs, declaringClass.getName()));
+ }
+
+ Class extends ActiveProfilesResolver> resolverClass = annAttrs.getClass("resolver");
+ if (!ActiveProfilesResolver.class.equals(resolverClass)) {
+ String msg = String.format("Configuration error for test class [%s]: %s cannot be used "
+ + "in conjunction with custom resolver [%s].", rootDeclaringClass.getName(),
+ getClass().getSimpleName(), resolverClass.getName());
+ logger.error(msg);
+ throw new IllegalStateException(msg);
+ }
+
+ String[] profiles = annAttrs.getStringArray("profiles");
+ String[] valueProfiles = annAttrs.getStringArray("value");
+ boolean valueDeclared = !ObjectUtils.isEmpty(valueProfiles);
+ boolean profilesDeclared = !ObjectUtils.isEmpty(profiles);
+
+ if (valueDeclared && profilesDeclared) {
+ String msg = String.format("Class [%s] has been configured with @ActiveProfiles' 'value' [%s] "
+ + "and 'profiles' [%s] attributes. Only one declaration of active bean "
+ + "definition profiles is permitted per @ActiveProfiles annotation.", declaringClass.getName(),
+ ObjectUtils.nullSafeToString(valueProfiles), ObjectUtils.nullSafeToString(profiles));
+ logger.error(msg);
+ throw new IllegalStateException(msg);
+ }
+
+ if (valueDeclared) {
+ profiles = valueProfiles;
+ }
+
+ for (String profile : profiles) {
+ if (StringUtils.hasText(profile)) {
+ activeProfiles.add(profile.trim());
+ }
+ }
+ }
+
+ return StringUtils.toStringArray(activeProfiles);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/DefaultTestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/support/DefaultTestContextBootstrapper.java
new file mode 100644
index 00000000000..d3cf3f87762
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/support/DefaultTestContextBootstrapper.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2002-2014 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.support;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.test.context.CacheAwareContextLoaderDelegate;
+import org.springframework.test.context.ContextLoader;
+import org.springframework.test.context.MergedContextConfiguration;
+import org.springframework.test.context.TestContextBootstrapper;
+import org.springframework.test.context.TestExecutionListener;
+
+/**
+ * Default implementation of the {@link TestContextBootstrapper} SPI.
+ *
+ *
+ * - Uses the following default {@link TestExecutionListener TestExecutionListeners}:
+ *
+ * - {@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener}
+ *
- {@link org.springframework.test.context.support.DirtiesContextTestExecutionListener}
+ *
- {@link org.springframework.test.context.transaction.TransactionalTestExecutionListener}
+ *
+ * - Uses {@link DelegatingSmartContextLoader} as the default {@link ContextLoader}.
+ *
- Builds a standard {@link MergedContextConfiguration}.
+ *
+ *
+ * @author Sam Brannen
+ * @since 4.1
+ */
+public class DefaultTestContextBootstrapper extends AbstractTestContextBootstrapper {
+
+ private static final List DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES = Collections.unmodifiableList(Arrays.asList(
+ "org.springframework.test.context.support.DependencyInjectionTestExecutionListener",
+ "org.springframework.test.context.support.DirtiesContextTestExecutionListener",
+ "org.springframework.test.context.transaction.TransactionalTestExecutionListener"));
+
+
+ /**
+ * Returns an unmodifiable list of fully qualified class names for the following
+ * default {@link TestExecutionListener TestExecutionListeners}:
+ *
+ * - {@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener}
+ *
- {@link org.springframework.test.context.support.DirtiesContextTestExecutionListener}
+ *
- {@link org.springframework.test.context.transaction.TransactionalTestExecutionListener}
+ *
+ */
+ protected List getDefaultTestExecutionListenerClassNames() {
+ return DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES;
+ }
+
+ /**
+ * Returns {@link DelegatingSmartContextLoader}.
+ */
+ @Override
+ protected Class extends ContextLoader> getDefaultContextLoaderClass(Class> testClass) {
+ return DelegatingSmartContextLoader.class;
+ }
+
+ /**
+ * Builds a standard {@link MergedContextConfiguration}.
+ */
+ protected MergedContextConfiguration buildMergedContextConfiguration(
+ Class> testClass,
+ String[] locations,
+ Class>[] classes,
+ Set>> initializerClasses,
+ String[] activeProfiles, ContextLoader contextLoader,
+ CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parentConfig) {
+
+ return new MergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles,
+ contextLoader, cacheAwareContextLoaderDelegate, parentConfig);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/web/WebAppConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/web/WebAppConfiguration.java
index 07746c1e142..b2434c5879e 100644
--- a/spring-test/src/main/java/org/springframework/test/context/web/WebAppConfiguration.java
+++ b/spring-test/src/main/java/org/springframework/test/context/web/WebAppConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2014 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.
@@ -23,6 +23,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import org.springframework.test.context.BootstrapWith;
+
/**
* {@code @WebAppConfiguration} is a class-level annotation that is used to
* declare that the {@code ApplicationContext} loaded for an integration test
@@ -51,6 +53,7 @@ import java.lang.annotation.Target;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
+@BootstrapWith(WebTestContextBootstrapper.class)
public @interface WebAppConfiguration {
/**
diff --git a/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java
new file mode 100644
index 00000000000..46b0e4ad292
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2002-2014 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.web;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.test.context.CacheAwareContextLoaderDelegate;
+import org.springframework.test.context.ContextLoader;
+import org.springframework.test.context.MergedContextConfiguration;
+import org.springframework.test.context.TestContextBootstrapper;
+import org.springframework.test.context.TestExecutionListener;
+import org.springframework.test.context.support.DefaultTestContextBootstrapper;
+
+/**
+ * Web-specific implementation of the {@link TestContextBootstrapper} SPI.
+ *
+ *
+ * - Prepends {@link org.springframework.test.context.web.ServletTestExecutionListener}
+ * to the list of default {@link TestExecutionListener TestExecutionListeners} supported by
+ * the superclass.
+ *
- Uses {@link WebDelegatingSmartContextLoader} as the default {@link ContextLoader}
+ * if the test class is annotated with {@link WebAppConfiguration @WebAppConfiguration}
+ * and otherwise delegates to the superclass.
+ *
- Builds a {@link WebMergedContextConfiguration} if the test class is annotated
+ * with {@link WebAppConfiguration @WebAppConfiguration} and otherwise delegates to
+ * the superclass.
+ *
+ *
+ * @author Sam Brannen
+ * @since 4.1
+ */
+public class WebTestContextBootstrapper extends DefaultTestContextBootstrapper {
+
+ /**
+ * Prepends {@link org.springframework.test.context.web.ServletTestExecutionListener}
+ * to the list of default {@link TestExecutionListener TestExecutionListeners}
+ * supported by the superclass and returns an unmodifiable, updated list.
+ */
+ @Override
+ protected List getDefaultTestExecutionListenerClassNames() {
+ List classNames = new ArrayList(super.getDefaultTestExecutionListenerClassNames());
+ classNames.add(0, "org.springframework.test.context.web.ServletTestExecutionListener");
+ return Collections.unmodifiableList(classNames);
+ }
+
+ /**
+ * Returns {@link WebDelegatingSmartContextLoader} if the supplied class is
+ * annotated with {@link WebAppConfiguration @WebAppConfiguration} and
+ * otherwise delegates to the superclass.
+ */
+ @Override
+ protected Class extends ContextLoader> getDefaultContextLoaderClass(Class> testClass) {
+ if (AnnotationUtils.findAnnotation(testClass, WebAppConfiguration.class) != null) {
+ return WebDelegatingSmartContextLoader.class;
+ }
+
+ // else...
+ return super.getDefaultContextLoaderClass(testClass);
+ }
+
+ /**
+ * Builds a {@link WebMergedContextConfiguration} if the supplied class is
+ * annotated with {@link WebAppConfiguration @WebAppConfiguration} and
+ * otherwise delegates to the superclass.
+ */
+ @Override
+ protected MergedContextConfiguration buildMergedContextConfiguration(
+ Class> testClass,
+ String[] locations,
+ Class>[] classes,
+ Set>> initializerClasses,
+ String[] activeProfiles, ContextLoader contextLoader,
+ CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parentConfig) {
+
+ WebAppConfiguration webAppConfiguration = AnnotationUtils.findAnnotation(testClass, WebAppConfiguration.class);
+ if (webAppConfiguration != null) {
+ String resourceBasePath = webAppConfiguration.value();
+
+ return new WebMergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles,
+ resourceBasePath, contextLoader, cacheAwareContextLoaderDelegate, parentConfig);
+ }
+
+ // else...
+ return super.buildMergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles,
+ contextLoader, cacheAwareContextLoaderDelegate, parentConfig);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/MetaAnnotationUtils.java b/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java
similarity index 92%
rename from spring-test/src/main/java/org/springframework/test/context/MetaAnnotationUtils.java
rename to spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java
index d926568d91b..6f556909811 100644
--- a/spring-test/src/main/java/org/springframework/test/context/MetaAnnotationUtils.java
+++ b/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.test.context;
+package org.springframework.test.util;
import java.lang.annotation.Annotation;
import java.util.HashSet;
@@ -27,8 +27,6 @@ import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
-import static org.springframework.core.annotation.AnnotationUtils.*;
-
/**
* {@code MetaAnnotationUtils} is a collection of utility methods that complements
* the standard support already available in {@link AnnotationUtils}.
@@ -36,24 +34,26 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
* Whereas {@code AnnotationUtils} provides utilities for getting or
* finding an annotation, {@code MetaAnnotationUtils} goes a step further
* by providing support for determining the root class on which an
- * annotation is declared, either directly or via a composed annotation.
- * This additional information is encapsulated in an {@link AnnotationDescriptor}.
+ * annotation is declared, either directly or indirectly via a composed
+ * annotation. This additional information is encapsulated in an
+ * {@link AnnotationDescriptor}.
*
*
The additional information provided by an {@code AnnotationDescriptor} is
* required by the Spring TestContext Framework in order to be able to
* support class hierarchy traversals for annotations such as
- * {@link ContextConfiguration @ContextConfiguration},
- * {@link TestExecutionListeners @TestExecutionListeners}, and
- * {@link ActiveProfiles @ActiveProfiles} which offer support for merging and
- * overriding various inherited annotation attributes (e.g., {@link
- * ContextConfiguration#inheritLocations}).
+ * {@link org.springframework.test.context.ContextConfiguration @ContextConfiguration},
+ * {@link org.springframework.test.context.TestExecutionListeners @TestExecutionListeners},
+ * and {@link org.springframework.test.context.ActiveProfiles @ActiveProfiles}
+ * which offer support for merging and overriding various inherited
+ * annotation attributes (e.g., {@link
+ * org.springframework.test.context.ContextConfiguration#inheritLocations}).
*
* @author Sam Brannen
* @since 4.0
* @see AnnotationUtils
* @see AnnotationDescriptor
*/
-abstract class MetaAnnotationUtils {
+public abstract class MetaAnnotationUtils {
private MetaAnnotationUtils() {
/* no-op */
@@ -117,13 +117,13 @@ abstract class MetaAnnotationUtils {
}
// Declared locally?
- if (isAnnotationDeclaredLocally(annotationType, clazz)) {
+ if (AnnotationUtils.isAnnotationDeclaredLocally(annotationType, clazz)) {
return new AnnotationDescriptor(clazz, clazz.getAnnotation(annotationType));
}
// Declared on a composed annotation (i.e., as a meta-annotation)?
for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) {
- if (!isInJavaLangAnnotationPackage(composedAnnotation) && visited.add(composedAnnotation)) {
+ if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedAnnotation) && visited.add(composedAnnotation)) {
AnnotationDescriptor descriptor = findAnnotationDescriptor(composedAnnotation.annotationType(),
visited, annotationType);
if (descriptor != null) {
@@ -203,14 +203,14 @@ abstract class MetaAnnotationUtils {
// Declared locally?
for (Class extends Annotation> annotationType : annotationTypes) {
- if (isAnnotationDeclaredLocally(annotationType, clazz)) {
+ if (AnnotationUtils.isAnnotationDeclaredLocally(annotationType, clazz)) {
return new UntypedAnnotationDescriptor(clazz, clazz.getAnnotation(annotationType));
}
}
// Declared on a composed annotation (i.e., as a meta-annotation)?
for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) {
- if (!isInJavaLangAnnotationPackage(composedAnnotation) && visited.add(composedAnnotation)) {
+ if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedAnnotation) && visited.add(composedAnnotation)) {
UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(
composedAnnotation.annotationType(), visited, annotationTypes);
if (descriptor != null) {
diff --git a/spring-test/src/test/java/org/springframework/test/context/BootstrapTestUtils.java b/spring-test/src/test/java/org/springframework/test/context/BootstrapTestUtils.java
new file mode 100644
index 00000000000..7af0039f022
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/BootstrapTestUtils.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2014 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;
+
+/**
+ * Collection of test-related utility methods for working with {@link BootstrapContext
+ * BootstrapContexts} and {@link TestContextBootstrapper TestContextBootstrappers}.
+ *
+ * @author Sam Brannen
+ * @since 4.1
+ */
+public abstract class BootstrapTestUtils {
+
+ private BootstrapTestUtils() {
+ /* no-op */
+ }
+
+ public static BootstrapContext buildBootstrapContext(Class> testClass,
+ CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
+ return new DefaultBootstrapContext(testClass, cacheAwareContextLoaderDelegate);
+ }
+
+ public static TestContextBootstrapper resolveTestContextBootstrapper(BootstrapContext bootstrapContext) {
+ return BootstrapUtils.resolveTestContextBootstrapper(bootstrapContext);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextCacheTests.java b/spring-test/src/test/java/org/springframework/test/context/ContextCacheTests.java
index d593ddb8e11..8818df46272 100644
--- a/spring-test/src/test/java/org/springframework/test/context/ContextCacheTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/ContextCacheTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2014 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.
@@ -56,7 +56,7 @@ public class ContextCacheTests {
}
private ApplicationContext loadContext(Class> testClass) {
- TestContext testContext = new DefaultTestContext(testClass, contextCache);
+ TestContext testContext = TestContextTestUtils.buildTestContext(testClass, contextCache);
return testContext.getApplicationContext();
}
@@ -119,15 +119,15 @@ public class ContextCacheTests {
public void removeContextHierarchyCacheLevel1() {
// Load Level 3-A
- TestContext testContext3a = new DefaultTestContext(ClassHierarchyContextHierarchyLevel3aTestCase.class,
- contextCache);
+ TestContext testContext3a = TestContextTestUtils.buildTestContext(
+ ClassHierarchyContextHierarchyLevel3aTestCase.class, contextCache);
testContext3a.getApplicationContext();
assertContextCacheStatistics(contextCache, "level 3, A", 3, 0, 3);
assertParentContextCount(2);
// Load Level 3-B
- TestContext testContext3b = new DefaultTestContext(ClassHierarchyContextHierarchyLevel3bTestCase.class,
- contextCache);
+ TestContext testContext3b = TestContextTestUtils.buildTestContext(
+ ClassHierarchyContextHierarchyLevel3bTestCase.class, contextCache);
testContext3b.getApplicationContext();
assertContextCacheStatistics(contextCache, "level 3, A and B", 4, 1, 4);
assertParentContextCount(2);
@@ -144,15 +144,15 @@ public class ContextCacheTests {
public void removeContextHierarchyCacheLevel1WithExhaustiveMode() {
// Load Level 3-A
- TestContext testContext3a = new DefaultTestContext(ClassHierarchyContextHierarchyLevel3aTestCase.class,
- contextCache);
+ TestContext testContext3a = TestContextTestUtils.buildTestContext(
+ ClassHierarchyContextHierarchyLevel3aTestCase.class, contextCache);
testContext3a.getApplicationContext();
assertContextCacheStatistics(contextCache, "level 3, A", 3, 0, 3);
assertParentContextCount(2);
// Load Level 3-B
- TestContext testContext3b = new DefaultTestContext(ClassHierarchyContextHierarchyLevel3bTestCase.class,
- contextCache);
+ TestContext testContext3b = TestContextTestUtils.buildTestContext(
+ ClassHierarchyContextHierarchyLevel3bTestCase.class, contextCache);
testContext3b.getApplicationContext();
assertContextCacheStatistics(contextCache, "level 3, A and B", 4, 1, 4);
assertParentContextCount(2);
@@ -169,15 +169,15 @@ public class ContextCacheTests {
public void removeContextHierarchyCacheLevel2() {
// Load Level 3-A
- TestContext testContext3a = new DefaultTestContext(ClassHierarchyContextHierarchyLevel3aTestCase.class,
- contextCache);
+ TestContext testContext3a = TestContextTestUtils.buildTestContext(
+ ClassHierarchyContextHierarchyLevel3aTestCase.class, contextCache);
testContext3a.getApplicationContext();
assertContextCacheStatistics(contextCache, "level 3, A", 3, 0, 3);
assertParentContextCount(2);
// Load Level 3-B
- TestContext testContext3b = new DefaultTestContext(ClassHierarchyContextHierarchyLevel3bTestCase.class,
- contextCache);
+ TestContext testContext3b = TestContextTestUtils.buildTestContext(
+ ClassHierarchyContextHierarchyLevel3bTestCase.class, contextCache);
testContext3b.getApplicationContext();
assertContextCacheStatistics(contextCache, "level 3, A and B", 4, 1, 4);
assertParentContextCount(2);
@@ -195,15 +195,15 @@ public class ContextCacheTests {
public void removeContextHierarchyCacheLevel2WithExhaustiveMode() {
// Load Level 3-A
- TestContext testContext3a = new DefaultTestContext(ClassHierarchyContextHierarchyLevel3aTestCase.class,
- contextCache);
+ TestContext testContext3a = TestContextTestUtils.buildTestContext(
+ ClassHierarchyContextHierarchyLevel3aTestCase.class, contextCache);
testContext3a.getApplicationContext();
assertContextCacheStatistics(contextCache, "level 3, A", 3, 0, 3);
assertParentContextCount(2);
// Load Level 3-B
- TestContext testContext3b = new DefaultTestContext(ClassHierarchyContextHierarchyLevel3bTestCase.class,
- contextCache);
+ TestContext testContext3b = TestContextTestUtils.buildTestContext(
+ ClassHierarchyContextHierarchyLevel3bTestCase.class, contextCache);
testContext3b.getApplicationContext();
assertContextCacheStatistics(contextCache, "level 3, A and B", 4, 1, 4);
assertParentContextCount(2);
@@ -219,15 +219,15 @@ public class ContextCacheTests {
public void removeContextHierarchyCacheLevel3Then2() {
// Load Level 3-A
- TestContext testContext3a = new DefaultTestContext(ClassHierarchyContextHierarchyLevel3aTestCase.class,
- contextCache);
+ TestContext testContext3a = TestContextTestUtils.buildTestContext(
+ ClassHierarchyContextHierarchyLevel3aTestCase.class, contextCache);
testContext3a.getApplicationContext();
assertContextCacheStatistics(contextCache, "level 3, A", 3, 0, 3);
assertParentContextCount(2);
// Load Level 3-B
- TestContext testContext3b = new DefaultTestContext(ClassHierarchyContextHierarchyLevel3bTestCase.class,
- contextCache);
+ TestContext testContext3b = TestContextTestUtils.buildTestContext(
+ ClassHierarchyContextHierarchyLevel3bTestCase.class, contextCache);
testContext3b.getApplicationContext();
assertContextCacheStatistics(contextCache, "level 3, A and B", 4, 1, 4);
assertParentContextCount(2);
@@ -248,15 +248,15 @@ public class ContextCacheTests {
public void removeContextHierarchyCacheLevel3Then2WithExhaustiveMode() {
// Load Level 3-A
- TestContext testContext3a = new DefaultTestContext(ClassHierarchyContextHierarchyLevel3aTestCase.class,
- contextCache);
+ TestContext testContext3a = TestContextTestUtils.buildTestContext(
+ ClassHierarchyContextHierarchyLevel3aTestCase.class, contextCache);
testContext3a.getApplicationContext();
assertContextCacheStatistics(contextCache, "level 3, A", 3, 0, 3);
assertParentContextCount(2);
// Load Level 3-B
- TestContext testContext3b = new DefaultTestContext(ClassHierarchyContextHierarchyLevel3bTestCase.class,
- contextCache);
+ TestContext testContext3b = TestContextTestUtils.buildTestContext(
+ ClassHierarchyContextHierarchyLevel3bTestCase.class, contextCache);
testContext3b.getApplicationContext();
assertContextCacheStatistics(contextCache, "level 3, A and B", 4, 1, 4);
assertParentContextCount(2);
diff --git a/spring-test/src/test/java/org/springframework/test/context/TestContextTestUtils.java b/spring-test/src/test/java/org/springframework/test/context/TestContextTestUtils.java
new file mode 100644
index 00000000000..94d7dbaba9a
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/TestContextTestUtils.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2014 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;
+
+/**
+ * Collection of test-related utility methods for working with {@link TestContext TestContexts}.
+ *
+ * @author Sam Brannen
+ * @since 4.1
+ */
+public abstract class TestContextTestUtils {
+
+ private TestContextTestUtils() {
+ /* no-op */
+ }
+
+ public static TestContext buildTestContext(Class> testClass, ContextCache contextCache) {
+ CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = new DefaultCacheAwareContextLoaderDelegate(
+ contextCache);
+ return buildTestContext(testClass, null, cacheAwareContextLoaderDelegate);
+ }
+
+ public static TestContext buildTestContext(Class> testClass, String customDefaultContextLoaderClassName,
+ CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
+ BootstrapContext bootstrapContext = new DefaultBootstrapContext(testClass, cacheAwareContextLoaderDelegate);
+ TestContextBootstrapper testContextBootstrapper = BootstrapUtils.resolveTestContextBootstrapper(bootstrapContext);
+
+ return new DefaultTestContext(testContextBootstrapper);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java b/spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java
index 32483a078cd..5142ee5a3f1 100644
--- a/spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2014 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.
@@ -45,7 +45,7 @@ public class TestExecutionListenersTests {
@Test
public void verifyNumDefaultListenersRegistered() throws Exception {
TestContextManager testContextManager = new TestContextManager(DefaultListenersExampleTestCase.class);
- assertEquals("Num registered TELs for DefaultListenersExampleTestCase.", 4,
+ assertEquals("Num registered TELs for DefaultListenersExampleTestCase.", 3,
testContextManager.getTestExecutionListeners().size());
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/CustomDefaultContextLoaderClassSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/CustomDefaultContextLoaderClassSpringRunnerTests.java
index 3ee77974a0e..74d5e465d9e 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/CustomDefaultContextLoaderClassSpringRunnerTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/CustomDefaultContextLoaderClassSpringRunnerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2014 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,27 +16,29 @@
package org.springframework.test.context.junit4;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.model.InitializationError;
-import org.springframework.tests.sample.beans.Pet;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.BootstrapWith;
import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextLoader;
+import org.springframework.test.context.support.DefaultTestContextBootstrapper;
import org.springframework.test.context.support.GenericPropertiesContextLoader;
+import org.springframework.tests.sample.beans.Pet;
+
+import static org.junit.Assert.*;
/**
- * Integration tests which verify that a subclass of {@link SpringJUnit4ClassRunner}
- * can specify a custom default ContextLoader class name that overrides
- * the standard default class name.
+ * Integration tests which verify that a subclass of {@link DefaultTestContextBootstrapper}
+ * can specify a custom default ContextLoader class that overrides the standard
+ * default class name.
*
* @author Sam Brannen
* @since 3.0
*/
-@RunWith(CustomDefaultContextLoaderClassSpringRunnerTests.PropertiesBasedSpringJUnit4ClassRunner.class)
-@ContextConfiguration(locations = "PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests-context.properties")
+@RunWith(SpringJUnit4ClassRunner.class)
+@BootstrapWith(CustomDefaultContextLoaderClassSpringRunnerTests.PropertiesBasedTestContextBootstrapper.class)
+@ContextConfiguration("PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests-context.properties")
public class CustomDefaultContextLoaderClassSpringRunnerTests {
@Autowired
@@ -56,16 +58,12 @@ public class CustomDefaultContextLoaderClassSpringRunnerTests {
}
- public static final class PropertiesBasedSpringJUnit4ClassRunner extends SpringJUnit4ClassRunner {
-
- public PropertiesBasedSpringJUnit4ClassRunner(Class> clazz) throws InitializationError {
- super(clazz);
- }
+ public static class PropertiesBasedTestContextBootstrapper extends DefaultTestContextBootstrapper {
@Override
- protected String getDefaultContextLoaderClassName(Class> clazz) {
- return GenericPropertiesContextLoader.class.getName();
+ protected Class extends ContextLoader> getDefaultContextLoaderClass(Class> testClass) {
+ return GenericPropertiesContextLoader.class;
}
-
}
+
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/AbstractContextLoaderUtilsTests$BareAnnotations-context.xml b/spring-test/src/test/java/org/springframework/test/context/support/AbstractContextLoaderUtilsTests$BareAnnotations-context.xml
similarity index 100%
rename from spring-test/src/test/java/org/springframework/test/context/AbstractContextLoaderUtilsTests$BareAnnotations-context.xml
rename to spring-test/src/test/java/org/springframework/test/context/support/AbstractContextLoaderUtilsTests$BareAnnotations-context.xml
diff --git a/spring-test/src/test/java/org/springframework/test/context/AbstractContextLoaderUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/support/AbstractContextLoaderUtilsTests.java
similarity index 78%
rename from spring-test/src/test/java/org/springframework/test/context/AbstractContextLoaderUtilsTests.java
rename to spring-test/src/test/java/org/springframework/test/context/support/AbstractContextLoaderUtilsTests.java
index 06515d42695..eae38bc594c 100644
--- a/spring-test/src/test/java/org/springframework/test/context/AbstractContextLoaderUtilsTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/support/AbstractContextLoaderUtilsTests.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.test.context;
+package org.springframework.test.context.support;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -23,10 +23,19 @@ import java.lang.annotation.Target;
import java.util.Collections;
import java.util.Set;
+import org.mockito.Mockito;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
-import org.springframework.test.context.support.AnnotationConfigContextLoader;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.BootstrapContext;
+import org.springframework.test.context.BootstrapTestUtils;
+import org.springframework.test.context.CacheAwareContextLoaderDelegate;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextConfigurationAttributes;
+import org.springframework.test.context.ContextLoader;
+import org.springframework.test.context.MergedContextConfiguration;
+import org.springframework.test.context.TestContextBootstrapper;
import static org.junit.Assert.*;
@@ -44,6 +53,14 @@ abstract class AbstractContextLoaderUtilsTests {
Collections.>> emptySet();
+ MergedContextConfiguration buildMergedContextConfiguration(Class> testClass) {
+ CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = Mockito.mock(CacheAwareContextLoaderDelegate.class);
+ BootstrapContext bootstrapContext = BootstrapTestUtils.buildBootstrapContext(testClass,
+ cacheAwareContextLoaderDelegate);
+ TestContextBootstrapper bootstrapper = BootstrapTestUtils.resolveTestContextBootstrapper(bootstrapContext);
+ return bootstrapper.buildMergedContextConfiguration();
+ }
+
void assertAttributes(ContextConfigurationAttributes attributes, Class> expectedDeclaringClass,
String[] expectedLocations, Class>[] expectedClasses,
Class extends ContextLoader> expectedContextLoaderClass, boolean expectedInheritLocations) {
@@ -173,4 +190,16 @@ abstract class AbstractContextLoaderUtilsTests {
static class OverriddenClassesBar extends ClassesFoo {
}
+ @ContextConfiguration(locations = "/foo.properties", loader = GenericPropertiesContextLoader.class)
+ @ActiveProfiles(profiles = "foo")
+ static class PropertiesLocationsFoo {
+ }
+
+ // Combining @Configuration classes with a Properties based loader doesn't really make
+ // sense, but that's OK for unit testing purposes.
+ @ContextConfiguration(classes = FooConfig.class, loader = GenericPropertiesContextLoader.class)
+ @ActiveProfiles(profiles = "foo")
+ static class PropertiesClassesFoo {
+ }
+
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsActiveProfilesTests.java b/spring-test/src/test/java/org/springframework/test/context/support/ActiveProfilesUtilsTests.java
similarity index 90%
rename from spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsActiveProfilesTests.java
rename to spring-test/src/test/java/org/springframework/test/context/support/ActiveProfilesUtilsTests.java
index 76e4e7b7bb0..f9f98b7c73c 100644
--- a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsActiveProfilesTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/support/ActiveProfilesUtilsTests.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.test.context;
+package org.springframework.test.context.support;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -25,19 +25,21 @@ import java.util.HashSet;
import java.util.Set;
import org.junit.Test;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.ActiveProfilesResolver;
import static org.junit.Assert.*;
-import static org.springframework.test.context.ContextLoaderUtils.*;
+import static org.springframework.test.context.support.ActiveProfilesUtils.*;
/**
- * Unit tests for {@link ContextLoaderUtils} involving resolution of active bean
+ * Unit tests for {@link ActiveProfilesUtils} involving resolution of active bean
* definition profiles.
*
* @author Sam Brannen
* @author Michail Nikolaev
* @since 3.1
*/
-public class ContextLoaderUtilsActiveProfilesTests extends AbstractContextLoaderUtilsTests {
+public class ActiveProfilesUtilsTests extends AbstractContextLoaderUtilsTests {
private void assertResolvedProfiles(Class> testClass, String... expected) {
assertNotNull(testClass);
@@ -168,17 +170,17 @@ public class ContextLoaderUtilsActiveProfilesTests extends AbstractContextLoader
/**
* @since 4.0
*/
- @Test(expected = IllegalStateException.class)
- public void resolveActiveProfilesWithConflictingResolverAndProfiles() {
- resolveActiveProfiles(ConflictingResolverAndProfilesTestCase.class);
+ @Test
+ public void resolveActiveProfilesWithResolverAndProfiles() {
+ assertResolvedProfiles(ResolverAndProfilesTestCase.class, "bar");
}
/**
* @since 4.0
*/
- @Test(expected = IllegalStateException.class)
- public void resolveActiveProfilesWithConflictingResolverAndValue() {
- resolveActiveProfiles(ConflictingResolverAndValueTestCase.class);
+ @Test
+ public void resolveActiveProfilesWithResolverAndValue() {
+ assertResolvedProfiles(ResolverAndValueTestCase.class, "bar");
}
/**
@@ -279,19 +281,19 @@ public class ContextLoaderUtilsActiveProfilesTests extends AbstractContextLoader
InheritedFooActiveProfilesResolverTestCase {
}
- @ActiveProfiles(resolver = BarActiveProfilesResolver.class, profiles = "conflict")
- private static class ConflictingResolverAndProfilesTestCase {
+ @ActiveProfiles(resolver = BarActiveProfilesResolver.class, profiles = "ignored by custom resolver")
+ private static class ResolverAndProfilesTestCase {
}
- @ActiveProfiles(resolver = BarActiveProfilesResolver.class, value = "conflict")
- private static class ConflictingResolverAndValueTestCase {
+ @ActiveProfiles(resolver = BarActiveProfilesResolver.class, value = "ignored by custom resolver")
+ private static class ResolverAndValueTestCase {
}
@MetaResolverConfig
private static class TestClassVerifyingActiveProfilesResolverTestCase {
}
- @ActiveProfiles(profiles = "conflict", value = "conflict")
+ @ActiveProfiles(profiles = "conflict 1", value = "conflict 2")
private static class ConflictingProfilesAndValueTestCase {
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsConfigurationAttributesTests.java b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsConfigurationAttributesTests.java
similarity index 94%
rename from spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsConfigurationAttributesTests.java
rename to spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsConfigurationAttributesTests.java
index 6025c2d47fd..a7f9a08ffa3 100644
--- a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsConfigurationAttributesTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsConfigurationAttributesTests.java
@@ -14,15 +14,19 @@
* limitations under the License.
*/
-package org.springframework.test.context;
+package org.springframework.test.context.support;
import java.util.List;
import org.junit.Test;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextConfigurationAttributes;
+import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
+import org.springframework.test.context.support.ContextLoaderUtils;
+import static org.springframework.test.context.support.ContextLoaderUtils.*;
import static org.junit.Assert.*;
-import static org.springframework.test.context.ContextLoaderUtils.*;
/**
* Unit tests for {@link ContextLoaderUtils} involving {@link ContextConfigurationAttributes}.
diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsContextHierarchyTests.java b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextHierarchyTests.java
similarity index 98%
rename from spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsContextHierarchyTests.java
rename to spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextHierarchyTests.java
index b1d07c02d14..052128f3931 100644
--- a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsContextHierarchyTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextHierarchyTests.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.test.context;
+package org.springframework.test.context.support;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -25,11 +25,16 @@ import java.util.Map;
import org.junit.Test;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextConfigurationAttributes;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
+import org.springframework.test.context.support.ContextLoaderUtils;
+import static org.springframework.test.context.support.ContextLoaderUtils.*;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
-import static org.springframework.test.context.ContextLoaderUtils.*;
/**
* Unit tests for {@link ContextLoaderUtils} involving context hierarchies.
diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsContextInitializerTests.java b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextInitializerTests.java
similarity index 93%
rename from spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsContextInitializerTests.java
rename to spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextInitializerTests.java
index 654efb2bd6e..03be7bd2567 100644
--- a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsContextInitializerTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextInitializerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2014 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.test.context;
+package org.springframework.test.context.support;
import java.util.HashSet;
import java.util.Set;
@@ -23,11 +23,10 @@ import org.junit.Test;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
-import org.springframework.test.context.support.DelegatingSmartContextLoader;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.web.context.support.GenericWebApplicationContext;
-import static org.springframework.test.context.ContextLoaderUtils.*;
-
/**
* Unit tests for {@link ContextLoaderUtils} involving {@link ApplicationContextInitializer}s.
*
@@ -44,7 +43,8 @@ public class ContextLoaderUtilsContextInitializerTests extends AbstractContextLo
= new HashSet>>();
expectedInitializerClasses.add(FooInitializer.class);
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
+
assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses,
DelegatingSmartContextLoader.class);
}
@@ -58,7 +58,8 @@ public class ContextLoaderUtilsContextInitializerTests extends AbstractContextLo
expectedInitializerClasses.add(FooInitializer.class);
expectedInitializerClasses.add(BarInitializer.class);
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
+
assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses,
DelegatingSmartContextLoader.class);
}
@@ -71,7 +72,8 @@ public class ContextLoaderUtilsContextInitializerTests extends AbstractContextLo
= new HashSet>>();
expectedInitializerClasses.add(BarInitializer.class);
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
+
assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses,
DelegatingSmartContextLoader.class);
}
@@ -84,7 +86,8 @@ public class ContextLoaderUtilsContextInitializerTests extends AbstractContextLo
= new HashSet>>();
expectedInitializerClasses.add(BarInitializer.class);
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
+
assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses,
DelegatingSmartContextLoader.class);
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsMergedConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsMergedConfigTests.java
similarity index 79%
rename from spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsMergedConfigTests.java
rename to spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsMergedConfigTests.java
index f9988392a4c..2d84d022f2c 100644
--- a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsMergedConfigTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsMergedConfigTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2014 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.
@@ -14,14 +14,11 @@
* limitations under the License.
*/
-package org.springframework.test.context;
+package org.springframework.test.context.support;
import org.junit.Test;
-import org.springframework.test.context.support.AnnotationConfigContextLoader;
-import org.springframework.test.context.support.DelegatingSmartContextLoader;
-import org.springframework.test.context.support.GenericPropertiesContextLoader;
-
-import static org.springframework.test.context.ContextLoaderUtils.*;
+import org.springframework.test.context.ContextLoader;
+import org.springframework.test.context.MergedContextConfiguration;
/**
* Unit tests for {@link ContextLoaderUtils} involving {@link MergedContextConfiguration}.
@@ -33,28 +30,28 @@ public class ContextLoaderUtilsMergedConfigTests extends AbstractContextLoaderUt
@Test
public void buildMergedConfigWithoutAnnotation() {
- Class testClass = Enigma.class;
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
+ Class> testClass = Enigma.class;
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, null);
}
@Test
public void buildMergedConfigWithBareAnnotations() {
- Class testClass = BareAnnotations.class;
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
+ Class> testClass = BareAnnotations.class;
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
assertMergedConfig(
mergedConfig,
testClass,
- new String[] { "classpath:/org/springframework/test/context/AbstractContextLoaderUtilsTests$BareAnnotations-context.xml" },
+ new String[] { "classpath:/org/springframework/test/context/support/AbstractContextLoaderUtilsTests$BareAnnotations-context.xml" },
EMPTY_CLASS_ARRAY, DelegatingSmartContextLoader.class);
}
@Test
public void buildMergedConfigWithLocalAnnotationAndLocations() {
Class> testClass = LocationsFoo.class;
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
assertMergedConfig(mergedConfig, testClass, new String[] { "classpath:/foo.xml" }, EMPTY_CLASS_ARRAY,
DelegatingSmartContextLoader.class);
@@ -63,7 +60,7 @@ public class ContextLoaderUtilsMergedConfigTests extends AbstractContextLoaderUt
@Test
public void buildMergedConfigWithMetaAnnotationAndLocations() {
Class> testClass = MetaLocationsFoo.class;
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
assertMergedConfig(mergedConfig, testClass, new String[] { "classpath:/foo.xml" }, EMPTY_CLASS_ARRAY,
DelegatingSmartContextLoader.class);
@@ -72,7 +69,7 @@ public class ContextLoaderUtilsMergedConfigTests extends AbstractContextLoaderUt
@Test
public void buildMergedConfigWithLocalAnnotationAndClasses() {
Class> testClass = ClassesFoo.class;
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, new Class>[] { FooConfig.class },
DelegatingSmartContextLoader.class);
@@ -80,21 +77,19 @@ public class ContextLoaderUtilsMergedConfigTests extends AbstractContextLoaderUt
@Test
public void buildMergedConfigWithLocalAnnotationAndOverriddenContextLoaderAndLocations() {
- Class> testClass = LocationsFoo.class;
+ Class> testClass = PropertiesLocationsFoo.class;
Class extends ContextLoader> expectedContextLoaderClass = GenericPropertiesContextLoader.class;
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass,
- expectedContextLoaderClass.getName(), null);
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
- assertMergedConfig(mergedConfig, testClass, new String[] { "classpath:/foo.xml" }, EMPTY_CLASS_ARRAY,
+ assertMergedConfig(mergedConfig, testClass, new String[] { "classpath:/foo.properties" }, EMPTY_CLASS_ARRAY,
expectedContextLoaderClass);
}
@Test
public void buildMergedConfigWithLocalAnnotationAndOverriddenContextLoaderAndClasses() {
- Class> testClass = ClassesFoo.class;
+ Class> testClass = PropertiesClassesFoo.class;
Class extends ContextLoader> expectedContextLoaderClass = GenericPropertiesContextLoader.class;
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass,
- expectedContextLoaderClass.getName(), null);
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, new Class>[] { FooConfig.class },
expectedContextLoaderClass);
@@ -104,8 +99,8 @@ public class ContextLoaderUtilsMergedConfigTests extends AbstractContextLoaderUt
public void buildMergedConfigWithLocalAndInheritedAnnotationsAndLocations() {
Class> testClass = LocationsBar.class;
String[] expectedLocations = new String[] { "/foo.xml", "/bar.xml" };
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
assertMergedConfig(mergedConfig, testClass, expectedLocations, EMPTY_CLASS_ARRAY,
AnnotationConfigContextLoader.class);
}
@@ -114,8 +109,8 @@ public class ContextLoaderUtilsMergedConfigTests extends AbstractContextLoaderUt
public void buildMergedConfigWithLocalAndInheritedAnnotationsAndClasses() {
Class> testClass = ClassesBar.class;
Class>[] expectedClasses = new Class>[] { FooConfig.class, BarConfig.class };
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses,
AnnotationConfigContextLoader.class);
}
@@ -124,8 +119,8 @@ public class ContextLoaderUtilsMergedConfigTests extends AbstractContextLoaderUt
public void buildMergedConfigWithAnnotationsAndOverriddenLocations() {
Class> testClass = OverriddenLocationsBar.class;
String[] expectedLocations = new String[] { "/bar.xml" };
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
assertMergedConfig(mergedConfig, testClass, expectedLocations, EMPTY_CLASS_ARRAY,
AnnotationConfigContextLoader.class);
}
@@ -134,8 +129,8 @@ public class ContextLoaderUtilsMergedConfigTests extends AbstractContextLoaderUt
public void buildMergedConfigWithAnnotationsAndOverriddenClasses() {
Class> testClass = OverriddenClassesBar.class;
Class>[] expectedClasses = new Class>[] { BarConfig.class };
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses,
AnnotationConfigContextLoader.class);
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/MetaAnnotationUtilsTests.java b/spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java
similarity index 98%
rename from spring-test/src/test/java/org/springframework/test/context/MetaAnnotationUtilsTests.java
rename to spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java
index 01a81d6456d..93cd0cecc91 100644
--- a/spring-test/src/test/java/org/springframework/test/context/MetaAnnotationUtilsTests.java
+++ b/spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.test.context;
+package org.springframework.test.util;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
@@ -27,12 +27,15 @@ import org.junit.Test;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
-import org.springframework.test.context.MetaAnnotationUtils.AnnotationDescriptor;
-import org.springframework.test.context.MetaAnnotationUtils.UntypedAnnotationDescriptor;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.util.MetaAnnotationUtils;
+import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
+import org.springframework.test.util.MetaAnnotationUtils.UntypedAnnotationDescriptor;
import org.springframework.transaction.annotation.Transactional;
+import static org.springframework.test.util.MetaAnnotationUtils.*;
+
import static org.junit.Assert.*;
-import static org.springframework.test.context.MetaAnnotationUtils.*;
/**
* Unit tests for {@link MetaAnnotationUtils}.
diff --git a/spring-test/src/test/java/org/springframework/test/context/OverriddenMetaAnnotationAttributesTests.java b/spring-test/src/test/java/org/springframework/test/util/OverriddenMetaAnnotationAttributesTests.java
similarity index 95%
rename from spring-test/src/test/java/org/springframework/test/context/OverriddenMetaAnnotationAttributesTests.java
rename to spring-test/src/test/java/org/springframework/test/util/OverriddenMetaAnnotationAttributesTests.java
index d2e1abee9f9..4b6e43eb75d 100644
--- a/spring-test/src/test/java/org/springframework/test/context/OverriddenMetaAnnotationAttributesTests.java
+++ b/spring-test/src/test/java/org/springframework/test/util/OverriddenMetaAnnotationAttributesTests.java
@@ -14,17 +14,20 @@
* limitations under the License.
*/
-package org.springframework.test.context;
+package org.springframework.test.util;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.junit.Test;
import org.springframework.core.annotation.AnnotationAttributes;
-import org.springframework.test.context.MetaAnnotationUtils.AnnotationDescriptor;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.util.MetaAnnotationUtils;
+import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
+
+import static org.springframework.test.util.MetaAnnotationUtils.*;
import static org.junit.Assert.*;
-import static org.springframework.test.context.MetaAnnotationUtils.*;
/**
* Unit tests for {@link MetaAnnotationUtils} that verify support for overridden