diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
index a1701c9db13..e177856e821 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
@@ -20,6 +20,7 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
+import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
@@ -240,14 +241,58 @@ public abstract class AnnotationUtils {
* if not found
* @see Class#isAnnotationPresent(Class)
* @see Class#getDeclaredAnnotations()
+ * @see #findAnnotationDeclaringClassForTypes(List, Class)
+ * @see #isAnnotationDeclaredLocally(Class, Class)
*/
public static Class> findAnnotationDeclaringClass(Class extends Annotation> annotationType, Class> clazz) {
Assert.notNull(annotationType, "Annotation type must not be null");
if (clazz == null || clazz.equals(Object.class)) {
return null;
}
- return (isAnnotationDeclaredLocally(annotationType, clazz)) ? clazz :
- findAnnotationDeclaringClass(annotationType, clazz.getSuperclass());
+ return (isAnnotationDeclaredLocally(annotationType, clazz)) ? clazz : findAnnotationDeclaringClass(
+ annotationType, clazz.getSuperclass());
+ }
+
+ /**
+ * Find the first {@link Class} in the inheritance hierarchy of the specified
+ * {@code clazz} (including the specified {@code clazz} itself) which declares
+ * at least one of the specified {@code annotationTypes}, or {@code null} if
+ * none of the specified annotation types could be found.
+ *
If the supplied {@code clazz} is {@code null}, {@code null} will be
+ * returned.
+ *
If the supplied {@code clazz} is an interface, only the interface itself
+ * will be checked; the inheritance hierarchy for interfaces will not be traversed.
+ *
The standard {@link Class} API does not provide a mechanism for determining
+ * which class in an inheritance hierarchy actually declares one of several
+ * candidate {@linkplain Annotation annotations}, so we need to handle this
+ * explicitly.
+ * @param annotationTypes the list of Class objects corresponding to the
+ * annotation types
+ * @param clazz the Class object corresponding to the class on which to check
+ * for the annotations, or {@code null}
+ * @return the first {@link Class} in the inheritance hierarchy of the specified
+ * {@code clazz} which declares an annotation of at least one of the specified
+ * {@code annotationTypes}, or {@code null} if not found
+ * @see Class#isAnnotationPresent(Class)
+ * @see Class#getDeclaredAnnotations()
+ * @see #findAnnotationDeclaringClass(Class, Class)
+ * @see #isAnnotationDeclaredLocally(Class, Class)
+ * @since 3.2.2
+ */
+ public static Class> findAnnotationDeclaringClassForTypes(List> annotationTypes,
+ Class> clazz) {
+ Assert.notEmpty(annotationTypes, "The list of annotation types must not be empty");
+ if (clazz == null || clazz.equals(Object.class)) {
+ return null;
+ }
+
+ for (Class extends Annotation> annotationType : annotationTypes) {
+ if (isAnnotationDeclaredLocally(annotationType, clazz)) {
+ return clazz;
+ }
+ }
+
+ return findAnnotationDeclaringClassForTypes(annotationTypes, clazz.getSuperclass());
}
/**
@@ -348,8 +393,8 @@ public abstract class AnnotationUtils {
* and corresponding attribute values as values
* @since 3.1.1
*/
- public static AnnotationAttributes getAnnotationAttributes(
- Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
+ public static AnnotationAttributes getAnnotationAttributes(Annotation annotation, boolean classValuesAsString,
+ boolean nestedAnnotationsAsMap) {
AnnotationAttributes attrs = new AnnotationAttributes();
Method[] methods = annotation.annotationType().getDeclaredMethods();
@@ -371,15 +416,15 @@ public abstract class AnnotationUtils {
}
}
if (nestedAnnotationsAsMap && value instanceof Annotation) {
- attrs.put(method.getName(), getAnnotationAttributes(
- (Annotation)value, classValuesAsString, nestedAnnotationsAsMap));
+ attrs.put(method.getName(),
+ getAnnotationAttributes((Annotation) value, classValuesAsString, nestedAnnotationsAsMap));
}
else if (nestedAnnotationsAsMap && value instanceof Annotation[]) {
- Annotation[] realAnnotations = (Annotation[])value;
+ Annotation[] realAnnotations = (Annotation[]) value;
AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length];
for (int i = 0; i < realAnnotations.length; i++) {
- mappedAnnotations[i] = getAnnotationAttributes(
- realAnnotations[i], classValuesAsString, nestedAnnotationsAsMap);
+ mappedAnnotations[i] = getAnnotationAttributes(realAnnotations[i], classValuesAsString,
+ nestedAnnotationsAsMap);
}
attrs.put(method.getName(), mappedAnnotations);
}
diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java
index 0c5d88f3343..9f7e76b0c92 100644
--- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java
+++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 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,21 +16,26 @@
package org.springframework.core.annotation;
+import static org.junit.Assert.*;
+import static org.springframework.core.annotation.AnnotationUtils.*;
+
+import java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+
import org.junit.Test;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
-import static org.junit.Assert.*;
-
-import static org.springframework.core.annotation.AnnotationUtils.*;
-
/**
+ * Unit tests for {@link AnnotationUtils}.
+ *
* @author Rod Johnson
* @author Juergen Hoeller
* @author Sam Brannen
@@ -102,23 +107,91 @@ public class AnnotationUtilsTests {
assertNull(findAnnotationDeclaringClass(Transactional.class, NonAnnotatedClass.class));
// inherited class-level annotation; note: @Transactional is inherited
- assertEquals(InheritedAnnotationInterface.class, findAnnotationDeclaringClass(Transactional.class,
- InheritedAnnotationInterface.class));
+ assertEquals(InheritedAnnotationInterface.class,
+ findAnnotationDeclaringClass(Transactional.class, InheritedAnnotationInterface.class));
assertNull(findAnnotationDeclaringClass(Transactional.class, SubInheritedAnnotationInterface.class));
- assertEquals(InheritedAnnotationClass.class, findAnnotationDeclaringClass(Transactional.class,
- InheritedAnnotationClass.class));
- assertEquals(InheritedAnnotationClass.class, findAnnotationDeclaringClass(Transactional.class,
- SubInheritedAnnotationClass.class));
+ assertEquals(InheritedAnnotationClass.class,
+ findAnnotationDeclaringClass(Transactional.class, InheritedAnnotationClass.class));
+ assertEquals(InheritedAnnotationClass.class,
+ findAnnotationDeclaringClass(Transactional.class, SubInheritedAnnotationClass.class));
// non-inherited class-level annotation; note: @Order is not inherited,
- // but findAnnotationDeclaringClass() should still find it.
- assertEquals(NonInheritedAnnotationInterface.class, findAnnotationDeclaringClass(Order.class,
- NonInheritedAnnotationInterface.class));
+ // but findAnnotationDeclaringClass() should still find it on classes.
+ assertEquals(NonInheritedAnnotationInterface.class,
+ findAnnotationDeclaringClass(Order.class, NonInheritedAnnotationInterface.class));
assertNull(findAnnotationDeclaringClass(Order.class, SubNonInheritedAnnotationInterface.class));
- assertEquals(NonInheritedAnnotationClass.class, findAnnotationDeclaringClass(Order.class,
- NonInheritedAnnotationClass.class));
- assertEquals(NonInheritedAnnotationClass.class, findAnnotationDeclaringClass(Order.class,
- SubNonInheritedAnnotationClass.class));
+ assertEquals(NonInheritedAnnotationClass.class,
+ findAnnotationDeclaringClass(Order.class, NonInheritedAnnotationClass.class));
+ assertEquals(NonInheritedAnnotationClass.class,
+ findAnnotationDeclaringClass(Order.class, SubNonInheritedAnnotationClass.class));
+ }
+
+ @Test
+ public void findAnnotationDeclaringClassForTypesWithSingleCandidateType() {
+
+ // no class-level annotation
+ List> transactionalCandidateList = Arrays.> asList(Transactional.class);
+ assertNull(findAnnotationDeclaringClassForTypes(transactionalCandidateList, NonAnnotatedInterface.class));
+ assertNull(findAnnotationDeclaringClassForTypes(transactionalCandidateList, NonAnnotatedClass.class));
+
+ // inherited class-level annotation; note: @Transactional is inherited
+ assertEquals(InheritedAnnotationInterface.class,
+ findAnnotationDeclaringClassForTypes(transactionalCandidateList, InheritedAnnotationInterface.class));
+ assertNull(findAnnotationDeclaringClassForTypes(transactionalCandidateList,
+ SubInheritedAnnotationInterface.class));
+ assertEquals(InheritedAnnotationClass.class,
+ findAnnotationDeclaringClassForTypes(transactionalCandidateList, InheritedAnnotationClass.class));
+ assertEquals(InheritedAnnotationClass.class,
+ findAnnotationDeclaringClassForTypes(transactionalCandidateList, SubInheritedAnnotationClass.class));
+
+ // non-inherited class-level annotation; note: @Order is not inherited,
+ // but findAnnotationDeclaringClassForTypes() should still find it on classes.
+ List> orderCandidateList = Arrays.> asList(Order.class);
+ assertEquals(NonInheritedAnnotationInterface.class,
+ findAnnotationDeclaringClassForTypes(orderCandidateList, NonInheritedAnnotationInterface.class));
+ assertNull(findAnnotationDeclaringClassForTypes(orderCandidateList, SubNonInheritedAnnotationInterface.class));
+ assertEquals(NonInheritedAnnotationClass.class,
+ findAnnotationDeclaringClassForTypes(orderCandidateList, NonInheritedAnnotationClass.class));
+ assertEquals(NonInheritedAnnotationClass.class,
+ findAnnotationDeclaringClassForTypes(orderCandidateList, SubNonInheritedAnnotationClass.class));
+ }
+
+ @Test
+ public void findAnnotationDeclaringClassForTypesWithMultipleCandidateTypes() {
+
+ List> candidates = Arrays.> asList(Transactional.class,
+ Order.class);
+
+ // no class-level annotation
+ assertNull(findAnnotationDeclaringClassForTypes(candidates, NonAnnotatedInterface.class));
+ assertNull(findAnnotationDeclaringClassForTypes(candidates, NonAnnotatedClass.class));
+
+ // inherited class-level annotation; note: @Transactional is inherited
+ assertEquals(InheritedAnnotationInterface.class,
+ findAnnotationDeclaringClassForTypes(candidates, InheritedAnnotationInterface.class));
+ assertNull(findAnnotationDeclaringClassForTypes(candidates, SubInheritedAnnotationInterface.class));
+ assertEquals(InheritedAnnotationClass.class,
+ findAnnotationDeclaringClassForTypes(candidates, InheritedAnnotationClass.class));
+ assertEquals(InheritedAnnotationClass.class,
+ findAnnotationDeclaringClassForTypes(candidates, SubInheritedAnnotationClass.class));
+
+ // non-inherited class-level annotation; note: @Order is not inherited,
+ // but findAnnotationDeclaringClassForTypes() should still find it on classes.
+ assertEquals(NonInheritedAnnotationInterface.class,
+ findAnnotationDeclaringClassForTypes(candidates, NonInheritedAnnotationInterface.class));
+ assertNull(findAnnotationDeclaringClassForTypes(candidates, SubNonInheritedAnnotationInterface.class));
+ assertEquals(NonInheritedAnnotationClass.class,
+ findAnnotationDeclaringClassForTypes(candidates, NonInheritedAnnotationClass.class));
+ assertEquals(NonInheritedAnnotationClass.class,
+ findAnnotationDeclaringClassForTypes(candidates, SubNonInheritedAnnotationClass.class));
+
+ // class hierarchy mixed with @Transactional and @Order declarations
+ assertEquals(TransactionalClass.class,
+ findAnnotationDeclaringClassForTypes(candidates, TransactionalClass.class));
+ assertEquals(TransactionalAndOrderedClass.class,
+ findAnnotationDeclaringClassForTypes(candidates, TransactionalAndOrderedClass.class));
+ assertEquals(TransactionalAndOrderedClass.class,
+ findAnnotationDeclaringClassForTypes(candidates, SubTransactionalAndOrderedClass.class));
}
@Test
@@ -216,18 +289,18 @@ public class AnnotationUtilsTests {
}
- @Component(value="meta1")
+ @Component(value = "meta1")
@Retention(RetentionPolicy.RUNTIME)
@interface Meta1 {
}
- @Component(value="meta2")
+ @Component(value = "meta2")
@Retention(RetentionPolicy.RUNTIME)
@interface Meta2 {
}
@Meta1
- @Component(value="local")
+ @Component(value = "local")
@Meta2
static class HasLocalAndMetaComponentAnnotation {
}
@@ -332,6 +405,16 @@ public class AnnotationUtilsTests {
public static class SubNonInheritedAnnotationClass extends NonInheritedAnnotationClass {
}
+ @Transactional
+ public static class TransactionalClass {
+ }
+
+ @Order
+ public static class TransactionalAndOrderedClass {
+ }
+
+ public static class SubTransactionalAndOrderedClass extends TransactionalAndOrderedClass {
+ }
public static interface InterfaceWithAnnotatedMethod {
@@ -353,10 +436,12 @@ public class AnnotationUtilsTests {
}
}
- public abstract static class AbstractDoesNotImplementInterfaceWithAnnotatedMethod implements InterfaceWithAnnotatedMethod {
+ public abstract static class AbstractDoesNotImplementInterfaceWithAnnotatedMethod implements
+ InterfaceWithAnnotatedMethod {
}
- public static class SubOfAbstractImplementsInterfaceWithAnnotatedMethod extends AbstractDoesNotImplementInterfaceWithAnnotatedMethod {
+ public static class SubOfAbstractImplementsInterfaceWithAnnotatedMethod extends
+ AbstractDoesNotImplementInterfaceWithAnnotatedMethod {
@Override
public void foo() {
diff --git a/spring-test/.springBeans b/spring-test/.springBeans
index 656622651c9..aa53e0e6588 100644
--- a/spring-test/.springBeans
+++ b/spring-test/.springBeans
@@ -10,6 +10,7 @@
src/test/java/org/springframework/test/context/junit4/profile/xml/DefaultProfileXmlConfigTests-context.xml
src/test/java/org/springframework/test/context/junit4/aci/xml/MultipleInitializersXmlConfigTests-context.xml
src/test/resources/org/springframework/test/context/web/RequestAndSessionScopedBeansWacTests-context.xml
+ src/test/resources/org/springframework/test/context/hierarchies/web/DispatcherWacRootWacEarTests-context.xml
diff --git a/spring-test/src/main/java/org/springframework/mock/jndi/SimpleNamingContextBuilder.java b/spring-test/src/main/java/org/springframework/mock/jndi/SimpleNamingContextBuilder.java
index 449c45facaf..5d2f70d5198 100644
--- a/spring-test/src/main/java/org/springframework/mock/jndi/SimpleNamingContextBuilder.java
+++ b/spring-test/src/main/java/org/springframework/mock/jndi/SimpleNamingContextBuilder.java
@@ -40,9 +40,9 @@ import org.springframework.util.ClassUtils;
*
* There are various choices for DataSource implementations:
*
- * - SingleConnectionDataSource (using the same Connection for all getConnection calls);
- *
- DriverManagerDataSource (creating a new Connection on each getConnection call);
- *
- Apache's Jakarta Commons DBCP offers BasicDataSource (a real pool).
+ *
- {@code SingleConnectionDataSource} (using the same Connection for all getConnection calls)
+ *
- {@code DriverManagerDataSource} (creating a new Connection on each getConnection call)
+ *
- Apache's Jakarta Commons DBCP offers {@code org.apache.commons.dbcp.BasicDataSource} (a real pool)
*
*
* Typical usage in bootstrap code:
@@ -77,7 +77,6 @@ import org.springframework.util.ClassUtils;
* @see SimpleNamingContext
* @see org.springframework.jdbc.datasource.SingleConnectionDataSource
* @see org.springframework.jdbc.datasource.DriverManagerDataSource
- * @see org.apache.commons.dbcp.BasicDataSource
*/
public class SimpleNamingContextBuilder implements InitialContextFactoryBuilder {
diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockServletContext.java b/spring-test/src/main/java/org/springframework/mock/web/MockServletContext.java
index 40fbabd1a17..76b65c3904c 100644
--- a/spring-test/src/main/java/org/springframework/mock/web/MockServletContext.java
+++ b/spring-test/src/main/java/org/springframework/mock/web/MockServletContext.java
@@ -163,6 +163,7 @@ public class MockServletContext implements ServletContext {
* @param resourceLoader the ResourceLoader to use (or null for the default)
* @see #registerNamedDispatcher
*/
+ @SuppressWarnings("javadoc")
public MockServletContext(String resourceBasePath, ResourceLoader resourceLoader) {
this.resourceLoader = (resourceLoader != null ? resourceLoader : new DefaultResourceLoader());
this.resourceBasePath = (resourceBasePath != null ? resourceBasePath : "");
@@ -344,6 +345,7 @@ public class MockServletContext implements ServletContext {
*
Defaults to {@linkplain #COMMON_DEFAULT_SERVLET_NAME "default"}.
* @see #setDefaultServletName
*/
+ @SuppressWarnings("javadoc")
public String getDefaultServletName() {
return this.defaultServletName;
}
diff --git a/spring-test/src/main/java/org/springframework/test/AbstractDependencyInjectionSpringContextTests.java b/spring-test/src/main/java/org/springframework/test/AbstractDependencyInjectionSpringContextTests.java
index 0ef706abe11..6229a8d3841 100644
--- a/spring-test/src/main/java/org/springframework/test/AbstractDependencyInjectionSpringContextTests.java
+++ b/spring-test/src/main/java/org/springframework/test/AbstractDependencyInjectionSpringContextTests.java
@@ -197,6 +197,7 @@ public abstract class AbstractDependencyInjectionSpringContextTests extends Abst
* test instance has not been configured
* @see #populateProtectedVariables()
*/
+ @SuppressWarnings("javadoc")
protected void injectDependencies() throws Exception {
Assert.state(getApplicationContext() != null,
"injectDependencies() called without first configuring an ApplicationContext");
diff --git a/spring-test/src/main/java/org/springframework/test/annotation/DirtiesContext.java b/spring-test/src/main/java/org/springframework/test/annotation/DirtiesContext.java
index 53a9b877e64..aa6ed211370 100644
--- a/spring-test/src/main/java/org/springframework/test/annotation/DirtiesContext.java
+++ b/spring-test/src/main/java/org/springframework/test/annotation/DirtiesContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 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.
@@ -36,13 +36,13 @@ import java.lang.annotation.Target;
* mode set to {@link ClassMode#AFTER_CLASS AFTER_CLASS}
*
*
- * Use this annotation if a test has modified the context (for example, by
- * replacing a bean definition). Subsequent tests will be supplied a new
- * context.
+ * Use this annotation if a test has modified the context — for example, by
+ * replacing a bean definition or changing the state of a singleton bean.
+ * Subsequent tests will be supplied a new context.
*
*
- * {@code @DirtiesContext} may be used as a class-level and
- * method-level annotation within the same class. In such scenarios, the
+ * {@code @DirtiesContext} may be used as a class-level and method-level
+ * annotation within the same class. In such scenarios, the
* {@code ApplicationContext} will be marked as dirty after any
* such annotated method as well as after the entire class. If the
* {@link ClassMode} is set to {@link ClassMode#AFTER_EACH_TEST_METHOD
@@ -53,16 +53,19 @@ import java.lang.annotation.Target;
* @author Sam Brannen
* @author Rod Johnson
* @since 2.0
+ * @see org.springframework.test.context.ContextConfiguration
*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
+@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface DirtiesContext {
/**
- * Defines modes which determine how {@code @DirtiesContext}
- * is interpreted when used to annotate a test class.
+ * Defines modes which determine how {@code @DirtiesContext} is
+ * interpreted when used to annotate a test class.
+ *
+ * @since 3.0
*/
static enum ClassMode {
@@ -76,18 +79,64 @@ public @interface DirtiesContext {
* The associated {@code ApplicationContext} will be marked as
* dirty after each test method in the class.
*/
- AFTER_EACH_TEST_METHOD
+ AFTER_EACH_TEST_METHOD;
+ }
+
+ /**
+ * Defines modes which determine how the context cache is cleared
+ * when {@code @DirtiesContext} is used in a test whose context is
+ * configured as part of a hierarchy via
+ * {@link org.springframework.test.context.ContextHierarchy @ContextHierarchy}.
+ *
+ * @since 3.2.2
+ */
+ static enum HierarchyMode {
+
+ /**
+ * The context cache will be cleared using an exhaustive algorithm
+ * that includes not only the {@linkplain HierarchyMode#CURRENT_LEVEL current level}
+ * but also all other context hierarchies that share an ancestor context
+ * common to the current test.
+ *
+ *
All {@code ApplicationContexts} that reside in a subhierarchy of
+ * the common ancestor context will be removed from the context cache and
+ * closed.
+ */
+ EXHAUSTIVE,
+
+ /**
+ * The {@code ApplicationContext} for the current level in the
+ * context hierarchy and all contexts in subhierarchies of the current
+ * level will be removed from the context cache and closed.
+ *
+ *
The current level refers to the {@code ApplicationContext}
+ * at the lowest level in the context hierarchy that is visible from the
+ * current test.
+ */
+ CURRENT_LEVEL;
}
/**
* The mode to use when a test class is annotated with
- * {@code @DirtiesContext}.
+ * {@code @DirtiesContext}.
*
Defaults to {@link ClassMode#AFTER_CLASS AFTER_CLASS}.
*
Note: Setting the class mode on an annotated test method has no meaning,
- * since the mere presence of the {@code @DirtiesContext}
- * annotation on a test method is sufficient.
+ * since the mere presence of the {@code @DirtiesContext} annotation on a
+ * test method is sufficient.
+ *
+ * @since 3.0
*/
ClassMode classMode() default ClassMode.AFTER_CLASS;
+ /**
+ * The context cache clearing mode to use when a context is
+ * configured as part of a hierarchy via
+ * {@link org.springframework.test.context.ContextHierarchy @ContextHierarchy}.
+ *
Defaults to {@link HierarchyMode#EXHAUSTIVE EXHAUSTIVE}.
+ *
+ * @since 3.2.2
+ */
+ HierarchyMode hierarchyMode() default HierarchyMode.EXHAUSTIVE;
+
}
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
new file mode 100644
index 00000000000..f7375def035
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/CacheAwareContextLoaderDelegate.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2002-2013 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.util.Assert;
+
+/**
+ * {@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.
+ *
+ *
Note: {@code CacheAwareContextLoaderDelegate} does not implement 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;
+ }
+
+ /**
+ * 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.
+ *
+ *
If the context is present in the cache it will simply be returned;
+ * otherwise, it will be loaded, stored in the cache, and returned.
+ * @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;
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextCache.java b/spring-test/src/main/java/org/springframework/test/context/ContextCache.java
index 32851cf5ed3..3a5a5b1adbd 100644
--- a/spring-test/src/main/java/org/springframework/test/context/ContextCache.java
+++ b/spring-test/src/main/java/org/springframework/test/context/ContextCache.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 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,24 +16,30 @@
package org.springframework.test.context;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.style.ToStringCreator;
+import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
import org.springframework.util.Assert;
/**
* Cache for Spring {@link ApplicationContext ApplicationContexts} in a test environment.
*
- *
Maintains a cache of {@link ApplicationContext contexts} keyed by
- * {@link MergedContextConfiguration} instances. This has significant performance
- * benefits if initializing the context would take time. While initializing a
- * Spring context itself is very quick, some beans in a context, such as a
- * {@code LocalSessionFactoryBean} for working with Hibernate, may take some time
- * to initialize. Hence it often makes sense to perform that initialization only
- * once per test suite.
+ *
Maintains a cache of {@code ApplicationContexts} keyed by
+ * {@link MergedContextConfiguration} instances.
+ *
+ *
This has significant performance benefits if initializing the context would take time.
+ * While initializing a Spring context itself is very quick, some beans in a context, such
+ * as a {@code LocalSessionFactoryBean} for working with Hibernate, may take some time to
+ * initialize. Hence it often makes sense to perform that initialization only once per
+ * test suite.
*
* @author Sam Brannen
* @author Juergen Hoeller
@@ -41,11 +47,22 @@ import org.springframework.util.Assert;
*/
class ContextCache {
+ private final Object monitor = new Object();
+
+ /**
+ * Map of context keys to Spring {@code ApplicationContext} instances.
+ */
+ private final Map contextMap = new ConcurrentHashMap(
+ 64);
+
/**
- * Map of context keys to Spring ApplicationContext instances.
+ * Map of parent keys to sets of children keys, representing a top-down tree
+ * of context hierarchies. This information is used for determining which subtrees
+ * need to be recursively removed and closed when removing a context that is a parent
+ * of other contexts.
*/
- private final Map contextMap =
- new ConcurrentHashMap(64);
+ private final Map> hierarchyMap = new ConcurrentHashMap>(
+ 64);
private int hitCount;
@@ -53,15 +70,18 @@ class ContextCache {
/**
- * Clears all contexts from the cache.
+ * Clears all contexts from the cache and clears context hierarchy information as
+ * well.
*/
void clear() {
- this.contextMap.clear();
+ synchronized (monitor) {
+ this.contextMap.clear();
+ this.hierarchyMap.clear();
+ }
}
/**
- * Clears hit and miss count statistics for the cache (i.e., resets counters
- * to zero).
+ * Clears hit and miss count statistics for the cache (i.e., resets counters to zero).
*/
void clearStatistics() {
this.hitCount = 0;
@@ -70,124 +90,210 @@ class ContextCache {
/**
* Return whether there is a cached context for the given key.
+ *
* @param key the context key (never {@code null})
*/
boolean contains(MergedContextConfiguration key) {
Assert.notNull(key, "Key must not be null");
- return this.contextMap.containsKey(key);
+ synchronized (monitor) {
+ return this.contextMap.containsKey(key);
+ }
}
/**
- * Obtain a cached ApplicationContext for the given key.
- * The {@link #getHitCount() hit} and {@link #getMissCount() miss}
- * counts will be updated accordingly.
+ * Obtain a cached {@code ApplicationContext} for the given key.
+ *
+ *
The {@link #getHitCount() hit} and {@link #getMissCount() miss} counts will be
+ * updated accordingly.
+ *
* @param key the context key (never {@code null})
- * @return the corresponding ApplicationContext instance,
- * or {@code null} if not found in the cache.
+ * @return the corresponding {@code ApplicationContext} instance, or {@code null} if
+ * not found in the cache.
* @see #remove
*/
ApplicationContext get(MergedContextConfiguration key) {
Assert.notNull(key, "Key must not be null");
- ApplicationContext context = this.contextMap.get(key);
- if (context == null) {
- incrementMissCount();
+ synchronized (monitor) {
+ ApplicationContext context = this.contextMap.get(key);
+ if (context == null) {
+ incrementMissCount();
+ }
+ else {
+ incrementHitCount();
+ }
+ return context;
}
- else {
- incrementHitCount();
- }
- return context;
}
/**
- * Increment the hit count by one. A hit is an access to the
- * cache, which returned a non-null context for a queried key.
+ * Increment the hit count by one. A hit is an access to the cache, which
+ * returned a non-null context for a queried key.
*/
private void incrementHitCount() {
this.hitCount++;
}
/**
- * Increment the miss count by one. A miss is an access to the
- * cache, which returned a {@code null} context for a queried key.
+ * Increment the miss count by one. A miss is an access to the cache, which
+ * returned a {@code null} context for a queried key.
*/
private void incrementMissCount() {
this.missCount++;
}
/**
- * Get the overall hit count for this cache. A hit is an access
- * to the cache, which returned a non-null context for a queried key.
+ * Get the overall hit count for this cache. A hit is an access to the cache,
+ * which returned a non-null context for a queried key.
*/
int getHitCount() {
return this.hitCount;
}
/**
- * Get the overall miss count for this cache. A miss is an
- * access to the cache, which returned a {@code null} context for a
- * queried key.
+ * Get the overall miss count for this cache. A miss is an access to the
+ * cache, which returned a {@code null} context for a queried key.
*/
int getMissCount() {
return this.missCount;
}
/**
- * Explicitly add an ApplicationContext instance to the cache under the given key.
+ * Explicitly add an {@code ApplicationContext} instance to the cache under the given
+ * key.
+ *
* @param key the context key (never {@code null})
- * @param context the ApplicationContext instance (never {@code null})
+ * @param context the {@code ApplicationContext} instance (never {@code null})
*/
void put(MergedContextConfiguration key, ApplicationContext context) {
Assert.notNull(key, "Key must not be null");
Assert.notNull(context, "ApplicationContext must not be null");
- this.contextMap.put(key, context);
- }
- /**
- * Remove the context with the given key.
- * @param key the context key (never {@code null})
- * @return the corresponding ApplicationContext instance, or {@code null}
- * if not found in the cache.
- * @see #setDirty
- */
- ApplicationContext remove(MergedContextConfiguration key) {
- return this.contextMap.remove(key);
+ synchronized (monitor) {
+ this.contextMap.put(key, context);
+
+ MergedContextConfiguration child = key;
+ MergedContextConfiguration parent = child.getParent();
+ while (parent != null) {
+ Set list = hierarchyMap.get(parent);
+ if (list == null) {
+ list = new HashSet();
+ hierarchyMap.put(parent, list);
+ }
+ list.add(child);
+ child = parent;
+ parent = child.getParent();
+ }
+ }
}
/**
- * Mark the context with the given key as dirty, effectively
- * {@link #remove removing} the context from the cache and explicitly
- * {@link ConfigurableApplicationContext#close() closing} it if it is an
+ * Remove the context with the given key from the cache and explicitly
+ * {@linkplain ConfigurableApplicationContext#close() close} it if it is an
* instance of {@link ConfigurableApplicationContext}.
+ *
* Generally speaking, you would only call this method if you change the
* state of a singleton bean, potentially affecting future interaction with
* the context.
- * @param key the context key (never {@code null})
- * @see #remove
+ *
+ *
In addition, the semantics of the supplied {@code HierarchyMode} will
+ * be honored. See the Javadoc for {@link HierarchyMode} for details.
+ *
+ * @param key the context key; never {@code null}
+ * @param hierarchyMode the hierarchy mode; may be {@code null} if the context
+ * is not part of a hierarchy
*/
- void setDirty(MergedContextConfiguration key) {
+ void remove(MergedContextConfiguration key, HierarchyMode hierarchyMode) {
Assert.notNull(key, "Key must not be null");
- ApplicationContext context = remove(key);
- if (context instanceof ConfigurableApplicationContext) {
- ((ConfigurableApplicationContext) context).close();
+
+ // startKey is the level at which to begin clearing the cache, depending
+ // on the configured hierarchy mode.
+ MergedContextConfiguration startKey = key;
+ if (hierarchyMode == HierarchyMode.EXHAUSTIVE) {
+ while (startKey.getParent() != null) {
+ startKey = startKey.getParent();
+ }
+ }
+
+ synchronized (monitor) {
+ final List removedContexts = new ArrayList();
+
+ remove(removedContexts, startKey);
+
+ // Remove all remaining references to any removed contexts from the
+ // hierarchy map.
+ for (MergedContextConfiguration currentKey : removedContexts) {
+ for (Set children : hierarchyMap.values()) {
+ children.remove(currentKey);
+ }
+ }
+
+ // Remove empty entries from the hierarchy map.
+ for (MergedContextConfiguration currentKey : hierarchyMap.keySet()) {
+ if (hierarchyMap.get(currentKey).isEmpty()) {
+ hierarchyMap.remove(currentKey);
+ }
+ }
+ }
+ }
+
+ private void remove(List removedContexts, MergedContextConfiguration key) {
+ Assert.notNull(key, "Key must not be null");
+
+ synchronized (monitor) {
+ Set children = hierarchyMap.get(key);
+ if (children != null) {
+ for (MergedContextConfiguration child : children) {
+ // Recurse through lower levels
+ remove(removedContexts, child);
+ }
+ // Remove the set of children for the current context from the
+ // hierarchy map.
+ hierarchyMap.remove(key);
+ }
+
+ // Physically remove and close leaf nodes first (i.e., on the way back up the
+ // stack as opposed to prior to the recursive call).
+ ApplicationContext context = contextMap.remove(key);
+ if (context instanceof ConfigurableApplicationContext) {
+ ((ConfigurableApplicationContext) context).close();
+ }
+ removedContexts.add(key);
}
}
/**
- * Determine the number of contexts currently stored in the cache. If the
- * cache contains more than Integer.MAX_VALUE elements, returns
+ * Determine the number of contexts currently stored in the cache. If the cache
+ * contains more than Integer.MAX_VALUE elements, returns
* Integer.MAX_VALUE.
*/
int size() {
- return this.contextMap.size();
+ synchronized (monitor) {
+ return this.contextMap.size();
+ }
+ }
+
+ /**
+ * Determine the number of parent contexts currently tracked within the cache.
+ */
+ int getParentContextCount() {
+ synchronized (monitor) {
+ return this.hierarchyMap.size();
+ }
}
/**
- * Generates a text string, which contains the {@link #size() size} as well
- * as the {@link #hitCount hit} and {@link #missCount miss} counts.
+ * Generates a text string, which contains the {@linkplain #size() size} as well
+ * as the {@linkplain #getHitCount() hit}, {@linkplain #getMissCount() miss}, and
+ * {@linkplain #getParentContextCount() parent context} counts.
*/
+ @Override
public String toString() {
- return new ToStringCreator(this).append("size", size()).append("hitCount", getHitCount()).
- append("missCount", getMissCount()).toString();
+ return new ToStringCreator(this)//
+ .append("size", size())//
+ .append("hitCount", getHitCount())//
+ .append("missCount", getMissCount())//
+ .append("parentContextCount", getParentContextCount())//
+ .toString();
}
}
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 a23aed849c7..b86032a7d24 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 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.
@@ -66,11 +66,12 @@ import org.springframework.context.ConfigurableApplicationContext;
*
* @author Sam Brannen
* @since 2.5
+ * @see ContextHierarchy
+ * @see ActiveProfiles
* @see ContextLoader
* @see SmartContextLoader
* @see ContextConfigurationAttributes
* @see MergedContextConfiguration
- * @see ActiveProfiles
* @see org.springframework.context.ApplicationContext
*/
@Documented
@@ -283,4 +284,19 @@ public @interface ContextConfiguration {
*/
Class extends ContextLoader> loader() default ContextLoader.class;
+ /**
+ * The name of the context hierarchy level represented by this configuration.
+ *
+ * If not specified the name will be inferred based on the numerical level within all
+ * declared contexts within the hierarchy.
+ *
+ *
This attribute is only applicable when used within a test class hierarchy that is
+ * configured using {@link ContextHierarchy @ContextHierarchy}, in which case the name
+ * can be used for merging or overriding this configuration with configuration of the
+ * same name in hierarchy levels defined in superclasses.
+ *
+ * @since 3.2.2
+ */
+ String name() default "";
+
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java b/spring-test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java
index 5a481c190de..035483a71e0 100644
--- a/spring-test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java
+++ b/spring-test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.test.context;
+import java.util.Arrays;
+
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -24,6 +26,7 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
/**
* {@code ContextConfigurationAttributes} encapsulates the context
@@ -54,6 +57,8 @@ public class ContextConfigurationAttributes {
private final boolean inheritInitializers;
+ private final String name;
+
/**
* Resolve resource locations from the {@link ContextConfiguration#locations() locations}
@@ -75,7 +80,8 @@ public class ContextConfigurationAttributes {
ObjectUtils.nullSafeToString(valueLocations), ObjectUtils.nullSafeToString(locations));
logger.error(msg);
throw new IllegalStateException(msg);
- } else if (!ObjectUtils.isEmpty(valueLocations)) {
+ }
+ else if (!ObjectUtils.isEmpty(valueLocations)) {
locations = valueLocations;
}
@@ -92,7 +98,7 @@ public class ContextConfigurationAttributes {
public ContextConfigurationAttributes(Class> declaringClass, ContextConfiguration contextConfiguration) {
this(declaringClass, resolveLocations(declaringClass, contextConfiguration), contextConfiguration.classes(),
contextConfiguration.inheritLocations(), contextConfiguration.initializers(),
- contextConfiguration.inheritInitializers(), contextConfiguration.loader());
+ contextConfiguration.inheritInitializers(), contextConfiguration.name(), contextConfiguration.loader());
}
/**
@@ -109,13 +115,13 @@ public class ContextConfigurationAttributes {
* @throws IllegalArgumentException if the {@code declaringClass} or {@code contextLoaderClass} is
* {@code null}, or if the {@code locations} and {@code classes} are both non-empty
* @deprecated as of Spring 3.2, use
- * {@link #ContextConfigurationAttributes(Class, String[], Class[], boolean, Class[], boolean, Class)}
+ * {@link #ContextConfigurationAttributes(Class, String[], Class[], boolean, Class[], boolean, String, Class)}
* instead
*/
@Deprecated
public ContextConfigurationAttributes(Class> declaringClass, String[] locations, Class>[] classes,
boolean inheritLocations, Class extends ContextLoader> contextLoaderClass) {
- this(declaringClass, locations, classes, inheritLocations, null, true, contextLoaderClass);
+ this(declaringClass, locations, classes, inheritLocations, null, true, null, contextLoaderClass);
}
/**
@@ -138,6 +144,31 @@ public class ContextConfigurationAttributes {
boolean inheritLocations,
Class extends ApplicationContextInitializer extends ConfigurableApplicationContext>>[] initializers,
boolean inheritInitializers, Class extends ContextLoader> contextLoaderClass) {
+ this(declaringClass, locations, classes, inheritLocations, initializers, inheritInitializers, null,
+ contextLoaderClass);
+ }
+
+ /**
+ * Construct a new {@link ContextConfigurationAttributes} instance for the
+ * {@linkplain Class test class} that declared the
+ * {@link ContextConfiguration @ContextConfiguration} annotation and its
+ * corresponding attributes.
+ *
+ * @param declaringClass the test class that declared {@code @ContextConfiguration}
+ * @param locations the resource locations declared via {@code @ContextConfiguration}
+ * @param classes the annotated classes declared via {@code @ContextConfiguration}
+ * @param inheritLocations the {@code inheritLocations} flag declared via {@code @ContextConfiguration}
+ * @param initializers the context initializers declared via {@code @ContextConfiguration}
+ * @param inheritInitializers the {@code inheritInitializers} flag declared via {@code @ContextConfiguration}
+ * @param name the name of level in the context hierarchy, or {@code null} if not applicable
+ * @param contextLoaderClass the {@code ContextLoader} class declared via {@code @ContextConfiguration}
+ * @throws IllegalArgumentException if the {@code declaringClass} or {@code contextLoaderClass} is
+ * {@code null}, or if the {@code locations} and {@code classes} are both non-empty
+ */
+ public ContextConfigurationAttributes(Class> declaringClass, String[] locations, Class>[] classes,
+ boolean inheritLocations,
+ Class extends ApplicationContextInitializer extends ConfigurableApplicationContext>>[] initializers,
+ boolean inheritInitializers, String name, Class extends ContextLoader> contextLoaderClass) {
Assert.notNull(declaringClass, "declaringClass must not be null");
Assert.notNull(contextLoaderClass, "contextLoaderClass must not be null");
@@ -158,6 +189,7 @@ public class ContextConfigurationAttributes {
this.inheritLocations = inheritLocations;
this.initializers = initializers;
this.inheritInitializers = inheritInitializers;
+ this.name = StringUtils.hasText(name) ? name : null;
this.contextLoaderClass = contextLoaderClass;
}
@@ -305,6 +337,101 @@ public class ContextConfigurationAttributes {
return contextLoaderClass;
}
+ /**
+ * Get the name of the context hierarchy level that was declared via
+ * {@link ContextConfiguration @ContextConfiguration}.
+ *
+ * @return the name of the context hierarchy level or {@code null} if not applicable
+ * @see ContextConfiguration#name()
+ * @since 3.2.2
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * Generate a unique hash code for all properties of this
+ * {@code ContextConfigurationAttributes} instance excluding the
+ * {@linkplain #getName() name}.
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + declaringClass.hashCode();
+ result = prime * result + Arrays.hashCode(locations);
+ result = prime * result + Arrays.hashCode(classes);
+ result = prime * result + (inheritLocations ? 1231 : 1237);
+ result = prime * result + Arrays.hashCode(initializers);
+ result = prime * result + (inheritInitializers ? 1231 : 1237);
+ result = prime * result + contextLoaderClass.hashCode();
+ return result;
+ }
+
+ /**
+ * Determine if the supplied object is equal to this
+ * {@code ContextConfigurationAttributes} instance by comparing both object's
+ * {@linkplain #getDeclaringClass() declaring class},
+ * {@linkplain #getLocations() locations},
+ * {@linkplain #getClasses() annotated classes},
+ * {@linkplain #isInheritLocations() inheritLocations flag},
+ * {@linkplain #getInitializers() context initializer classes},
+ * {@linkplain #isInheritInitializers() inheritInitializers flag}, and the
+ * {@link #getContextLoaderClass() ContextLoader class}.
+ */
+ @Override
+ public boolean equals(Object obj) {
+
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof ContextConfigurationAttributes)) {
+ return false;
+ }
+
+ final ContextConfigurationAttributes that = (ContextConfigurationAttributes) obj;
+
+ if (this.declaringClass == null) {
+ if (that.declaringClass != null) {
+ return false;
+ }
+ }
+ else if (!this.declaringClass.equals(that.declaringClass)) {
+ return false;
+ }
+
+ if (!Arrays.equals(this.locations, that.locations)) {
+ return false;
+ }
+
+ if (!Arrays.equals(this.classes, that.classes)) {
+ return false;
+ }
+
+ if (this.inheritLocations != that.inheritLocations) {
+ return false;
+ }
+
+ if (!Arrays.equals(this.initializers, that.initializers)) {
+ return false;
+ }
+
+ if (this.inheritInitializers != that.inheritInitializers) {
+ return false;
+ }
+
+ if (this.contextLoaderClass == null) {
+ if (that.contextLoaderClass != null) {
+ return false;
+ }
+ }
+ else if (!this.contextLoaderClass.equals(that.contextLoaderClass)) {
+ return false;
+ }
+
+ return true;
+ }
+
/**
* Provide a String representation of the context configuration attributes
* and declaring class.
@@ -318,6 +445,7 @@ public class ContextConfigurationAttributes {
.append("inheritLocations", inheritLocations)//
.append("initializers", ObjectUtils.nullSafeToString(initializers))//
.append("inheritInitializers", inheritInitializers)//
+ .append("name", name)//
.append("contextLoaderClass", contextLoaderClass.getName())//
.toString();
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextHierarchy.java b/spring-test/src/main/java/org/springframework/test/context/ContextHierarchy.java
new file mode 100644
index 00000000000..b7846b7a0d8
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/ContextHierarchy.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2002-2013 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 @ContextHierarchy} is a class-level annotation that is used to define
+ * a hierarchy of {@link org.springframework.context.ApplicationContext
+ * ApplicationContexts} for integration tests.
+ *
+ * @author Sam Brannen
+ * @since 3.2.2
+ * @see ContextConfiguration
+ * @see org.springframework.context.ApplicationContext
+ */
+@Documented
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface ContextHierarchy {
+
+ /**
+ * A list of {@link ContextConfiguration @ContextConfiguration} instances,
+ * each of which defines a level in the context hierarchy.
+ *
+ *
If you need to merge or override the configuration for a given level
+ * of the context hierarchy within a test class hierarchy, you must explicitly
+ * name that level by supplying the same value to the {@link ContextConfiguration#name
+ * name} attribute in {@code @ContextConfiguration} at each level in the
+ * class hierarchy.
+ */
+ ContextConfiguration[] value();
+
+}
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
index 8016c5d7c77..a43897eb0b6 100644
--- a/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java
+++ b/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java
@@ -16,20 +16,24 @@
package org.springframework.test.context;
-import static org.springframework.beans.BeanUtils.*;
-import static org.springframework.core.annotation.AnnotationUtils.*;
+import static org.springframework.beans.BeanUtils.instantiateClass;
+import static org.springframework.core.annotation.AnnotationUtils.findAnnotationDeclaringClass;
+import static org.springframework.core.annotation.AnnotationUtils.findAnnotationDeclaringClassForTypes;
+import static org.springframework.core.annotation.AnnotationUtils.isAnnotationDeclaredLocally;
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.AnnotationUtils;
@@ -52,10 +56,13 @@ import org.springframework.util.StringUtils;
* @see ContextConfigurationAttributes
* @see ActiveProfiles
* @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";
@@ -70,30 +77,29 @@ abstract class ContextLoaderUtils {
}
/**
- * Resolve the {@link ContextLoader} {@linkplain Class class} to use for the
- * supplied list of {@link ContextConfigurationAttributes} and then
- * instantiate and return that {@code ContextLoader}.
+ * 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 {@value #DEFAULT_CONTEXT_LOADER_CLASS_NAME}
- * or {@value #DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME} will be used as the
- * default context loader class name. For details on the class resolution
- * process, see {@link #resolveContextLoaderClass}.
+ *
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
+ * @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})
+ * @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
*/
- @SuppressWarnings("javadoc")
static ContextLoader resolveContextLoader(Class> testClass,
List configAttributesList, String defaultContextLoaderClassName) {
Assert.notNull(testClass, "Class must not be null");
@@ -113,36 +119,35 @@ abstract class ContextLoaderUtils {
}
/**
- * Resolve the {@link ContextLoader} {@linkplain Class class} to use for the
- * supplied list of {@link ContextConfigurationAttributes}.
+ * 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:
+ *
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}.
+ * - 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
+ * @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
+ * @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
+ * @throws IllegalStateException if the default {@code ContextLoader} class could not
+ * be loaded
*/
@SuppressWarnings("unchecked")
static Class extends ContextLoader> resolveContextLoaderClass(Class> testClass,
@@ -184,22 +189,201 @@ abstract class ContextLoaderUtils {
}
/**
- * Resolve the list of {@link ContextConfigurationAttributes configuration
- * attributes} for the supplied {@link Class class} and its superclasses.
+ * Convenience method for creating a {@link ContextConfigurationAttributes} instance
+ * from the supplied {@link ContextConfiguration} 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);
+ }
+
+ /**
+ * 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; if a given class in the class hierarchy
+ * declares both {@code @ContextConfiguration} and {@code @ContextHierarchy} as
+ * top-level annotations; or if individual {@code @ContextConfiguration}
+ * elements within a {@code @ContextHierarchy} declaration on a given class
+ * in the class hierarchy do not define unique context configuration.
+ *
+ * @since 3.2.2
+ * @see #buildContextHierarchyMap(Class)
+ * @see #resolveContextConfigurationAttributes(Class)
+ */
+ static List> resolveContextHierarchyAttributes(Class> testClass) {
+ Assert.notNull(testClass, "Class must not be null");
+
+ final Class contextConfigType = ContextConfiguration.class;
+ final Class contextHierarchyType = ContextHierarchy.class;
+ final List> annotationTypes = Arrays.asList(contextConfigType, contextHierarchyType);
+
+ final List> hierarchyAttributes = new ArrayList>();
+
+ Class> declaringClass = findAnnotationDeclaringClassForTypes(annotationTypes, testClass);
+ Assert.notNull(declaringClass, 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 (declaringClass != null) {
+
+ boolean contextConfigDeclaredLocally = isAnnotationDeclaredLocally(contextConfigType, declaringClass);
+ boolean contextHierarchyDeclaredLocally = isAnnotationDeclaredLocally(contextHierarchyType, declaringClass);
+
+ if (contextConfigDeclaredLocally && contextHierarchyDeclaredLocally) {
+ String msg = String.format("Test class [%s] has been configured with both @ContextConfiguration "
+ + "and @ContextHierarchy as class-level annotations. Only one of these annotations may "
+ + "be declared as a top-level annotation per test class.", declaringClass.getName());
+ logger.error(msg);
+ throw new IllegalStateException(msg);
+ }
+
+ final List configAttributesList = new ArrayList();
+
+ if (contextConfigDeclaredLocally) {
+ ContextConfiguration contextConfiguration = declaringClass.getAnnotation(contextConfigType);
+ convertContextConfigToConfigAttributesAndAddToList(contextConfiguration, declaringClass,
+ configAttributesList);
+ }
+ else if (contextHierarchyDeclaredLocally) {
+ ContextHierarchy contextHierarchy = declaringClass.getAnnotation(contextHierarchyType);
+ for (ContextConfiguration contextConfiguration : contextHierarchy.value()) {
+ convertContextConfigToConfigAttributesAndAddToList(contextConfiguration, declaringClass,
+ configAttributesList);
+ }
+
+ // Check for uniqueness
+ Set configAttributesSet = new HashSet(
+ configAttributesList);
+ if (configAttributesSet.size() != configAttributesList.size()) {
+ String msg = String.format("The @ContextConfiguration elements configured via "
+ + "@ContextHierarchy in test class [%s] must define unique contexts to load.",
+ declaringClass.getName());
+ logger.error(msg);
+ throw new IllegalStateException(msg);
+ }
+ }
+ else {
+ // This should theoretically actually never happen...
+ String msg = String.format("Test class [%s] has been configured with neither @ContextConfiguration "
+ + "nor @ContextHierarchy as a class-level annotation.", declaringClass.getName());
+ logger.error(msg);
+ throw new IllegalStateException(msg);
+ }
+
+ hierarchyAttributes.add(0, configAttributesList);
+
+ declaringClass = findAnnotationDeclaringClassForTypes(annotationTypes, declaringClass.getSuperclass());
+ }
+
+ 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}
+ *
+ * @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);
+ }
+ }
+
+ 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.
+ *
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
+ * @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");
@@ -214,19 +398,7 @@ abstract class ContextLoaderUtils {
while (declaringClass != null) {
ContextConfiguration contextConfiguration = declaringClass.getAnnotation(annotationType);
- 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);
-
+ convertContextConfigToConfigAttributesAndAddToList(contextConfiguration, declaringClass, attributesList);
declaringClass = findAnnotationDeclaringClass(annotationType, declaringClass.getSuperclass());
}
@@ -234,22 +406,21 @@ abstract class ContextLoaderUtils {
}
/**
- * Resolve the list of merged {@code ApplicationContextInitializer} classes
- * for the supplied list of {@code ContextConfigurationAttributes}.
+ * 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.
+ * 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
+ * @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})
+ * @return the set of merged context initializer classes, including those from
+ * superclasses if appropriate (never {@code null})
* @since 3.2
*/
static Set>> resolveInitializerClasses(
@@ -278,16 +449,15 @@ abstract class ContextLoaderUtils {
/**
* 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.
+ *
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})
+ * @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
*/
@@ -342,14 +512,72 @@ abstract class ContextLoaderUtils {
}
/**
- * Build the {@link MergedContextConfiguration merged context configuration}
- * for the supplied {@link Class testClass} and
- * {@code defaultContextLoaderClassName}.
+ * 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("javadoc")
+ static MergedContextConfiguration buildMergedContextConfiguration(Class> testClass,
+ String defaultContextLoaderClassName, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
+
+ if (testClass.isAnnotationPresent(ContextHierarchy.class)) {
+ 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 defaultContextLoaderClassName the name of the default
- * {@code ContextLoader} class to use (may 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
@@ -358,10 +586,11 @@ abstract class ContextLoaderUtils {
* @see #resolveActiveProfiles
* @see MergedContextConfiguration
*/
- static MergedContextConfiguration buildMergedContextConfiguration(Class> testClass,
- String defaultContextLoaderClassName) {
+ private static MergedContextConfiguration buildMergedContextConfiguration(final Class> testClass,
+ final List configAttributesList,
+ final String defaultContextLoaderClassName, MergedContextConfiguration parentConfig,
+ CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
- final List configAttributesList = resolveContextConfigurationAttributes(testClass);
final ContextLoader contextLoader = resolveContextLoader(testClass, configAttributesList,
defaultContextLoaderClassName);
final List locationsList = new ArrayList();
@@ -397,22 +626,21 @@ abstract class ContextLoaderUtils {
String[] activeProfiles = resolveActiveProfiles(testClass);
MergedContextConfiguration mergedConfig = buildWebMergedContextConfiguration(testClass, locations, classes,
- initializerClasses, activeProfiles, contextLoader);
+ initializerClasses, activeProfiles, contextLoader, cacheAwareContextLoaderDelegate, parentConfig);
if (mergedConfig == null) {
mergedConfig = new MergedContextConfiguration(testClass, locations, classes, initializerClasses,
- activeProfiles, contextLoader);
+ activeProfiles, contextLoader, cacheAwareContextLoaderDelegate, parentConfig);
}
return mergedConfig;
}
/**
- * Load the {@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration}
+ * 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
+ * @return the {@code @WebAppConfiguration} class or {@code null} if it cannot be loaded
* @since 3.2
*/
@SuppressWarnings("unchecked")
@@ -431,12 +659,10 @@ abstract class ContextLoaderUtils {
}
/**
- * Attempt to build a {@link org.springframework.test.context.web.WebMergedContextConfiguration
- * WebMergedContextConfiguration} from the supplied arguments, using reflection
- * in order to avoid package cycles.
+ * 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
+ * @return the {@code WebMergedContextConfiguration} or {@code null} if it could not be built
* @since 3.2
*/
@SuppressWarnings("unchecked")
@@ -445,7 +671,8 @@ abstract class ContextLoaderUtils {
String[] locations,
Class>[] classes,
Set>> initializerClasses,
- String[] activeProfiles, ContextLoader contextLoader) {
+ String[] activeProfiles, ContextLoader contextLoader,
+ CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parentConfig) {
Class extends Annotation> webAppConfigClass = loadWebAppConfigurationClass();
@@ -459,11 +686,12 @@ abstract class ContextLoaderUtils {
Constructor extends MergedContextConfiguration> constructor = ClassUtils.getConstructorIfAvailable(
webMergedConfigClass, Class.class, String[].class, Class[].class, Set.class, String[].class,
- String.class, ContextLoader.class);
+ String.class, ContextLoader.class, CacheAwareContextLoaderDelegate.class,
+ MergedContextConfiguration.class);
if (constructor != null) {
return instantiateClass(constructor, testClass, locations, classes, initializerClasses,
- activeProfiles, resourceBasePath, contextLoader);
+ activeProfiles, resourceBasePath, contextLoader, cacheAwareContextLoaderDelegate, parentConfig);
}
}
catch (Throwable t) {
diff --git a/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java
index b4c1f683a2c..befe85c7cba 100644
--- a/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java
+++ b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 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,9 +23,11 @@ import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
+import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.style.ToStringCreator;
+import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@@ -72,6 +74,8 @@ public class MergedContextConfiguration implements Serializable {
private final Set>> contextInitializerClasses;
private final String[] activeProfiles;
private final ContextLoader contextLoader;
+ private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate;
+ private final MergedContextConfiguration parent;
private static String[] processLocations(String[] locations) {
@@ -149,6 +153,7 @@ public class MergedContextConfiguration implements Serializable {
* @param contextInitializerClasses the merged context initializer classes
* @param activeProfiles the merged active bean definition profiles
* @param contextLoader the resolved {@code ContextLoader}
+ * @see #MergedContextConfiguration(Class, String[], Class[], Set, String[], ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)
*/
public MergedContextConfiguration(
Class> testClass,
@@ -156,12 +161,48 @@ public class MergedContextConfiguration implements Serializable {
Class>[] classes,
Set>> contextInitializerClasses,
String[] activeProfiles, ContextLoader contextLoader) {
+ this(testClass, locations, classes, contextInitializerClasses, activeProfiles, contextLoader, null, null);
+ }
+
+ /**
+ * Create a new {@code MergedContextConfiguration} instance for the
+ * supplied test class, resource locations, annotated classes, context
+ * initializers, active profiles, {@code ContextLoader}, and parent
+ * configuration.
+ *
+ * If a {@code null} value is supplied for {@code locations},
+ * {@code classes}, or {@code activeProfiles} an empty array will
+ * be stored instead. If a {@code null} value is supplied for the
+ * {@code contextInitializerClasses} an empty set will be stored instead.
+ * Furthermore, active profiles will be sorted, and duplicate profiles will
+ * be removed.
+ *
+ * @param testClass the test class for which the configuration was merged
+ * @param locations the merged resource locations
+ * @param classes the merged annotated classes
+ * @param contextInitializerClasses the merged context initializer classes
+ * @param activeProfiles the merged active bean definition profiles
+ * @param contextLoader the resolved {@code ContextLoader}
+ * @param cacheAwareContextLoaderDelegate a cache-aware context loader
+ * delegate with which to retrieve the parent context
+ * @param parent the parent configuration or {@code null} if there is no parent
+ * @since 3.2.2
+ */
+ public MergedContextConfiguration(
+ Class> testClass,
+ String[] locations,
+ Class>[] classes,
+ Set>> contextInitializerClasses,
+ String[] activeProfiles, ContextLoader contextLoader,
+ CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) {
this.testClass = testClass;
this.locations = processLocations(locations);
this.classes = processClasses(classes);
this.contextInitializerClasses = processContextInitializerClasses(contextInitializerClasses);
this.activeProfiles = processActiveProfiles(activeProfiles);
this.contextLoader = contextLoader;
+ this.cacheAwareContextLoaderDelegate = cacheAwareContextLoaderDelegate;
+ this.parent = parent;
}
/**
@@ -207,6 +248,39 @@ public class MergedContextConfiguration implements Serializable {
return contextLoader;
}
+ /**
+ * Get the {@link MergedContextConfiguration} for the parent application context in a
+ * context hierarchy.
+ *
+ * @return the parent configuration or {@code null} if there is no parent
+ * @see #getParentApplicationContext()
+ * @since 3.2.2
+ */
+ public MergedContextConfiguration getParent() {
+ return this.parent;
+ }
+
+ /**
+ * Get the parent {@link ApplicationContext} for the context defined by this
+ * {@code MergedContextConfiguration} from the context cache.
+ *
+ * If the parent context has not yet been loaded, it will be loaded, stored in the
+ * cache, and then returned.
+ *
+ * @return the parent {@code ApplicationContext} or {@code null} if there is no parent
+ * @see #getParent()
+ * @since 3.2.2
+ */
+ public ApplicationContext getParentApplicationContext() {
+ if (parent == null) {
+ return null;
+ }
+
+ Assert.state(cacheAwareContextLoaderDelegate != null,
+ "Cannot retrieve a parent application context without access to the CacheAwareContextLoaderDelegate.");
+ return cacheAwareContextLoaderDelegate.loadContext(parent);
+ }
+
/**
* Generate a unique hash code for all properties of this
* {@code MergedContextConfiguration} excluding the
@@ -220,6 +294,7 @@ public class MergedContextConfiguration implements Serializable {
result = prime * result + Arrays.hashCode(classes);
result = prime * result + contextInitializerClasses.hashCode();
result = prime * result + Arrays.hashCode(activeProfiles);
+ result = prime * result + (parent == null ? 0 : parent.hashCode());
result = prime * result + nullSafeToString(contextLoader).hashCode();
return result;
}
@@ -229,8 +304,9 @@ public class MergedContextConfiguration implements Serializable {
* instance by comparing both object's {@linkplain #getLocations() locations},
* {@linkplain #getClasses() annotated classes},
* {@linkplain #getContextInitializerClasses() context initializer classes},
- * {@linkplain #getActiveProfiles() active profiles}, and the fully qualified
- * names of their {@link #getContextLoader() ContextLoaders}.
+ * {@linkplain #getActiveProfiles() active profiles},
+ * {@linkplain #getParent() parents}, and the fully qualified names of their
+ * {@link #getContextLoader() ContextLoaders}.
*/
@Override
public boolean equals(Object obj) {
@@ -247,15 +323,28 @@ public class MergedContextConfiguration implements Serializable {
if (!Arrays.equals(this.locations, that.locations)) {
return false;
}
+
if (!Arrays.equals(this.classes, that.classes)) {
return false;
}
+
if (!this.contextInitializerClasses.equals(that.contextInitializerClasses)) {
return false;
}
+
if (!Arrays.equals(this.activeProfiles, that.activeProfiles)) {
return false;
}
+
+ if (this.parent == null) {
+ if (that.parent != null) {
+ return false;
+ }
+ }
+ else if (!this.parent.equals(that.parent)) {
+ return false;
+ }
+
if (!nullSafeToString(this.contextLoader).equals(nullSafeToString(that.contextLoader))) {
return false;
}
@@ -267,8 +356,9 @@ public class MergedContextConfiguration implements Serializable {
* Provide a String representation of the {@linkplain #getTestClass() test class},
* {@linkplain #getLocations() locations}, {@linkplain #getClasses() annotated classes},
* {@linkplain #getContextInitializerClasses() context initializer classes},
- * {@linkplain #getActiveProfiles() active profiles}, and the name of the
- * {@link #getContextLoader() ContextLoader}.
+ * {@linkplain #getActiveProfiles() active profiles}, the name of the
+ * {@link #getContextLoader() ContextLoader}, and the
+ * {@linkplain #getParent() parent configuration}.
*/
@Override
public String toString() {
@@ -279,6 +369,7 @@ public class MergedContextConfiguration implements Serializable {
.append("contextInitializerClasses", ObjectUtils.nullSafeToString(contextInitializerClasses))//
.append("activeProfiles", ObjectUtils.nullSafeToString(activeProfiles))//
.append("contextLoader", nullSafeToString(contextLoader))//
+ .append("parent", parent)//
.toString();
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/SmartContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/SmartContextLoader.java
index 8d069c85e91..d0495ca010b 100644
--- a/spring-test/src/main/java/org/springframework/test/context/SmartContextLoader.java
+++ b/spring-test/src/main/java/org/springframework/test/context/SmartContextLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 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.
@@ -29,8 +29,7 @@ import org.springframework.context.ApplicationContext;
* context that it loads (see {@link MergedContextConfiguration#getActiveProfiles()}
* and {@link #loadContext(MergedContextConfiguration)}).
*
- *
See the Javadoc for
- * {@link ContextConfiguration @ContextConfiguration}
+ *
See the Javadoc for {@link ContextConfiguration @ContextConfiguration}
* for a definition of annotated class.
*
*
Clients of a {@code SmartContextLoader} should call
@@ -48,8 +47,8 @@ import org.springframework.context.ApplicationContext;
*
Even though {@code SmartContextLoader} extends {@code ContextLoader},
* clients should favor {@code SmartContextLoader}-specific methods over those
* defined in {@code ContextLoader}, particularly because a
- * {@code SmartContextLoader} may choose not to support methods defined in
- * the {@code ContextLoader} SPI.
+ * {@code SmartContextLoader} may choose not to support methods defined in the
+ * {@code ContextLoader} SPI.
*
*
Concrete implementations must provide a {@code public} no-args constructor.
*
@@ -59,6 +58,9 @@ import org.springframework.context.ApplicationContext;
*
{@link org.springframework.test.context.support.AnnotationConfigContextLoader AnnotationConfigContextLoader}
* {@link org.springframework.test.context.support.GenericXmlContextLoader GenericXmlContextLoader}
* {@link org.springframework.test.context.support.GenericPropertiesContextLoader GenericPropertiesContextLoader}
+ * {@link org.springframework.test.context.web.WebDelegatingSmartContextLoader WebDelegatingSmartContextLoader}
+ * {@link org.springframework.test.context.web.AnnotationConfigWebContextLoader AnnotationConfigWebContextLoader}
+ * {@link org.springframework.test.context.web.GenericXmlWebContextLoader GenericXmlWebContextLoader}
*
*
* @author Sam Brannen
diff --git a/spring-test/src/main/java/org/springframework/test/context/TestContext.java b/spring-test/src/main/java/org/springframework/test/context/TestContext.java
index 27100be94c1..d2719b50a0a 100644
--- a/spring-test/src/main/java/org/springframework/test/context/TestContext.java
+++ b/spring-test/src/main/java/org/springframework/test/context/TestContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 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.
@@ -20,9 +20,11 @@ import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+
import org.springframework.context.ApplicationContext;
import org.springframework.core.AttributeAccessorSupport;
import org.springframework.core.style.ToStringCreator;
+import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
import org.springframework.util.Assert;
/**
@@ -41,6 +43,8 @@ public class TestContext extends AttributeAccessorSupport {
private final ContextCache contextCache;
+ private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate;
+
private final MergedContextConfiguration mergedContextConfiguration;
private final Class> testClass;
@@ -61,16 +65,17 @@ public class TestContext extends AttributeAccessorSupport {
}
/**
- * Construct a new test context for the supplied {@link Class test class}
- * and {@link ContextCache context cache} and parse the corresponding
- * {@link ContextConfiguration @ContextConfiguration} annotation, if
- * present.
+ * 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 the {@code @ContextConfiguration}
- * annotation, a
+ * class is explicitly supplied via {@code @ContextConfiguration}, a
* {@link org.springframework.test.context.support.DelegatingSmartContextLoader
- * DelegatingSmartContextLoader} will be used instead.
+ * 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
@@ -83,54 +88,27 @@ public class TestContext extends AttributeAccessorSupport {
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);
+
MergedContextConfiguration mergedContextConfiguration;
- ContextConfiguration contextConfiguration = testClass.getAnnotation(ContextConfiguration.class);
- if (contextConfiguration == null) {
- if (logger.isInfoEnabled()) {
- logger.info(String.format("@ContextConfiguration not found for class [%s]", testClass));
- }
- mergedContextConfiguration = new MergedContextConfiguration(testClass, null, null, null, null);
+ if (testClass.isAnnotationPresent(ContextConfiguration.class)
+ || testClass.isAnnotationPresent(ContextHierarchy.class)) {
+ mergedContextConfiguration = ContextLoaderUtils.buildMergedContextConfiguration(testClass,
+ defaultContextLoaderClassName, cacheAwareContextLoaderDelegate);
}
else {
- if (logger.isTraceEnabled()) {
- logger.trace(String.format("Retrieved @ContextConfiguration [%s] for class [%s]", contextConfiguration,
- testClass));
+ if (logger.isInfoEnabled()) {
+ logger.info(String.format(
+ "Neither @ContextConfiguration nor @ContextHierarchy found for test class [%s]",
+ testClass.getName()));
}
- mergedContextConfiguration = ContextLoaderUtils.buildMergedContextConfiguration(testClass,
- defaultContextLoaderClassName);
+ mergedContextConfiguration = new MergedContextConfiguration(testClass, null, null, null, null);
}
- this.contextCache = contextCache;
this.mergedContextConfiguration = mergedContextConfiguration;
- this.testClass = testClass;
- }
-
- /**
- * Load an {@code ApplicationContext} for this test context using the
- * configured {@code ContextLoader} and 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 loadApplicationContext() throws Exception {
- ContextLoader contextLoader = mergedContextConfiguration.getContextLoader();
- Assert.notNull(contextLoader, "Cannot load an ApplicationContext with a NULL 'contextLoader'. "
- + "Consider annotating your test class with @ContextConfiguration.");
-
- 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.");
- applicationContext = contextLoader.loadContext(locations);
- }
-
- return applicationContext;
}
/**
@@ -141,31 +119,7 @@ public class TestContext extends AttributeAccessorSupport {
* application context
*/
public ApplicationContext getApplicationContext() {
- synchronized (contextCache) {
- ApplicationContext context = contextCache.get(mergedContextConfiguration);
- if (context == null) {
- try {
- context = loadApplicationContext();
- if (logger.isDebugEnabled()) {
- logger.debug(String.format(
- "Storing ApplicationContext for test class [%s] in cache under key [%s].", testClass,
- 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 for test class [%s] from cache with key [%s].", testClass,
- mergedContextConfiguration));
- }
- }
- return context;
- }
+ return cacheAwareContextLoaderDelegate.loadContext(mergedContextConfiguration);
}
/**
@@ -209,15 +163,27 @@ public class TestContext extends AttributeAccessorSupport {
}
/**
- * Call this method to signal that the {@link ApplicationContext application
- * context} associated with this test context is dirty and should
- * be reloaded. Do this if a test has modified the context (for example, by
- * replacing a bean definition).
+ * Call this method to signal that the {@linkplain ApplicationContext application
+ * context} associated with this test context is dirty and should be
+ * discarded. Do this if a test has modified the context — for example,
+ * by replacing a bean definition or modifying the state of a singleton bean.
+ * @deprecated As of Spring 3.2.2, use {@link #markApplicationContextDirty(HierarchyMode)} instead.
*/
+ @Deprecated
public void markApplicationContextDirty() {
- synchronized (contextCache) {
- contextCache.setDirty(mergedContextConfiguration);
- }
+ markApplicationContextDirty((HierarchyMode) null);
+ }
+
+ /**
+ * Call this method to signal that the {@linkplain ApplicationContext application
+ * context} associated with this test context is dirty and should be
+ * discarded. Do this if a test has modified the context — for example,
+ * by replacing a bean definition or modifying the state of a singleton bean.
+ * @param hierarchyMode the context cache clearing mode to be applied if the
+ * context is part of a hierarchy (may be {@code null})
+ */
+ public void markApplicationContextDirty(HierarchyMode hierarchyMode) {
+ contextCache.remove(mergedContextConfiguration, hierarchyMode);
}
/**
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 c67f0c72d75..aabdf33dfb7 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
@@ -104,15 +104,14 @@ public class TestContextManager {
}
/**
- * Constructs a new {@code TestContextManager} for the specified {@link Class test class}
- * and automatically {@link #registerTestExecutionListeners registers} the
+ * 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})
+ * @param defaultContextLoaderClassName the name of the default {@code ContextLoader}
+ * class to use (may be {@code null})
* @see #registerTestExecutionListeners(TestExecutionListener...)
- * @see #retrieveTestExecutionListeners(Class)
*/
public TestContextManager(Class> testClass, String defaultContextLoaderClassName) {
this.testContext = new TestContext(testClass, contextCache, defaultContextLoaderClassName);
diff --git a/spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java
index 0f2ceb73a48..a052523fc90 100644
--- a/spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java
+++ b/spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 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.
@@ -17,29 +17,24 @@
package org.springframework.test.context;
/**
+ * {@code TestExecutionListener} defines a listener API for reacting to
+ * test execution events published by the {@link TestContextManager} with which
+ * the listener is registered.
*
- * {@code TestExecutionListener} defines a listener API for
- * reacting to test execution events published by the {@link TestContextManager}
- * with which the listener is registered.
- *
- *
- * Concrete implementations must provide a {@code public} no-args
- * constructor, so that listeners can be instantiated transparently by tools and
- * configuration mechanisms.
- *
+ * Concrete implementations must provide a {@code public} no-args constructor,
+ * so that listeners can be instantiated transparently by tools and configuration
+ * mechanisms.
*
* Spring provides the following out-of-the-box implementations:
- *
*
- * -
- * {@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener
+ *
- {@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener
* DependencyInjectionTestExecutionListener}
- * -
- * {@link org.springframework.test.context.support.DirtiesContextTestExecutionListener
+ *
- {@link org.springframework.test.context.support.DirtiesContextTestExecutionListener
* DirtiesContextTestExecutionListener}
- * -
- * {@link org.springframework.test.context.transaction.TransactionalTestExecutionListener
+ *
- {@link org.springframework.test.context.transaction.TransactionalTestExecutionListener
* TransactionalTestExecutionListener}
+ * - {@link org.springframework.test.context.web.ServletTestExecutionListener
+ * ServletTestExecutionListener}
*
*
* @author Sam Brannen
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 50cfa74474e..388f66b5c8e 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-2012 the original author or authors.
+ * Copyright 2002-2013 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.
@@ -26,9 +26,10 @@ import java.lang.annotation.Target;
/**
* {@code TestExecutionListeners} defines class-level metadata for
* configuring which {@link TestExecutionListener TestExecutionListeners} should
- * be registered with a {@link TestContextManager}. Typically,
- * {@code @TestExecutionListeners} will be used in conjunction with
- * {@link ContextConfiguration @ContextConfiguration}.
+ * be registered with a {@link TestContextManager}.
+ *
+ * Typically, {@code @TestExecutionListeners} will be used in conjunction with
+ * {@link ContextConfiguration @ContextConfiguration}.
*
* @author Sam Brannen
* @since 2.5
@@ -43,11 +44,10 @@ import java.lang.annotation.Target;
public @interface TestExecutionListeners {
/**
- *
* The {@link TestExecutionListener TestExecutionListeners} to register with
* a {@link TestContextManager}.
- *
*
+ * @see org.springframework.test.context.web.ServletTestExecutionListener
* @see org.springframework.test.context.support.DependencyInjectionTestExecutionListener
* @see org.springframework.test.context.support.DirtiesContextTestExecutionListener
* @see org.springframework.test.context.transaction.TransactionalTestExecutionListener
@@ -60,10 +60,8 @@ public @interface TestExecutionListeners {
Class extends TestExecutionListener>[] value() default {};
/**
- *
* Whether or not {@link #value() TestExecutionListeners} from superclasses
* should be inherited.
- *
*
* The default value is {@code true}, which means that an annotated
* class will inherit the listeners defined by an annotated
@@ -77,11 +75,12 @@ public @interface TestExecutionListeners {
* {@code DependencyInjectionTestExecutionListener},
* {@code DirtiesContextTestExecutionListener}, and
* {@code TransactionalTestExecutionListener}, in that order.
- *
*
*
- * @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
- * DirtiesContextTestExecutionListener.class })
+ * @TestExecutionListeners({
+ * DependencyInjectionTestExecutionListener.class,
+ * DirtiesContextTestExecutionListener.class
+ * })
* public abstract class AbstractBaseTest {
* // ...
* }
@@ -89,14 +88,12 @@ public @interface TestExecutionListeners {
* @TestExecutionListeners(TransactionalTestExecutionListener.class)
* public class TransactionalTest extends AbstractBaseTest {
* // ...
- * }
- *
+ * }
*
*
- * If {@code inheritListeners} is set to {@code false}, the
- * listeners for the annotated class will shadow and effectively
- * replace any listeners defined by a superclass.
- *
+ * If {@code inheritListeners} is set to {@code false}, the listeners for the
+ * annotated class will shadow and effectively replace any listeners
+ * defined by a superclass.
*/
boolean inheritListeners() default true;
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java
index c4b9b4dca7e..bd84068ac78 100644
--- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java
+++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 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,10 +16,13 @@
package org.springframework.test.context.support;
+
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.support.GenericApplicationContext;
@@ -66,6 +69,12 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
*
*
* - Creates a {@link GenericApplicationContext} instance.
+ * - If the supplied {@code MergedContextConfiguration} references a
+ * {@linkplain MergedContextConfiguration#getParent() parent configuration},
+ * the corresponding {@link MergedContextConfiguration#getParentApplicationContext()
+ * ApplicationContext} will be retrieved and
+ * {@linkplain GenericApplicationContext#setParent(ApplicationContext) set as the parent}
+ * for the context created by this method.
* - Calls {@link #prepareContext(GenericApplicationContext)} for backwards
* compatibility with the {@link org.springframework.test.context.ContextLoader
* ContextLoader} SPI.
@@ -97,6 +106,11 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
}
GenericApplicationContext context = new GenericApplicationContext();
+
+ ApplicationContext parent = mergedConfig.getParentApplicationContext();
+ if (parent != null) {
+ context.setParent(parent);
+ }
prepareContext(context);
prepareContext(context, mergedConfig);
customizeBeanFactory(context.getDefaultListableBeanFactory());
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/DirtiesContextTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/support/DirtiesContextTestExecutionListener.java
index 7c784741899..f865d532dae 100644
--- a/spring-test/src/main/java/org/springframework/test/context/support/DirtiesContextTestExecutionListener.java
+++ b/spring-test/src/main/java/org/springframework/test/context/support/DirtiesContextTestExecutionListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 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,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
+import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
import org.springframework.test.context.TestContext;
import org.springframework.util.Assert;
@@ -43,26 +44,47 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi
/**
- * Marks the {@link ApplicationContext application context} of the supplied
- * {@link TestContext test context} as
- * {@link TestContext#markApplicationContextDirty() dirty}, and sets the
+ * Marks the {@linkplain ApplicationContext application context} of the supplied
+ * {@linkplain TestContext test context} as
+ * {@linkplain TestContext#markApplicationContextDirty() dirty}, and sets the
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
* REINJECT_DEPENDENCIES_ATTRIBUTE} in the test context to {@code true}.
+ * @param testContext the test context whose application context should
+ * marked as dirty
+ * @deprecated as of Spring 3.2.2, use {@link #dirtyContext(TestContext, HierarchyMode)} instead.
*/
+ @Deprecated
protected void dirtyContext(TestContext testContext) {
testContext.markApplicationContextDirty();
testContext.setAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE, Boolean.TRUE);
}
/**
- * If the current test method of the supplied {@link TestContext test
+ * Marks the {@linkplain ApplicationContext application context} of the supplied
+ * {@linkplain TestContext test context} as {@linkplain
+ * TestContext#markApplicationContextDirty(HierarchyMode) dirty} and sets the
+ * {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
+ * REINJECT_DEPENDENCIES_ATTRIBUTE} in the test context to {@code true}.
+ * @param testContext the test context whose application context should
+ * marked as dirty
+ * @param hierarchyMode the context cache clearing mode to be applied if the
+ * context is part of a hierarchy; may be {@code null}
+ * @since 3.2.2
+ */
+ protected void dirtyContext(TestContext testContext, HierarchyMode hierarchyMode) {
+ testContext.markApplicationContextDirty(hierarchyMode);
+ testContext.setAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE, Boolean.TRUE);
+ }
+
+ /**
+ * If the current test method of the supplied {@linkplain TestContext test
* context} is annotated with {@link DirtiesContext @DirtiesContext},
* or if the test class is annotated with {@link DirtiesContext
- * @DirtiesContext} and the {@link DirtiesContext#classMode() class
+ * @DirtiesContext} and the {@linkplain DirtiesContext#classMode() class
* mode} is set to {@link ClassMode#AFTER_EACH_TEST_METHOD
- * AFTER_EACH_TEST_METHOD}, the {@link ApplicationContext application
+ * AFTER_EACH_TEST_METHOD}, the {@linkplain ApplicationContext application
* context} of the test context will be
- * {@link TestContext#markApplicationContextDirty() marked as dirty} and the
+ * {@linkplain TestContext#markApplicationContextDirty() marked as dirty} and the
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
* REINJECT_DEPENDENCIES_ATTRIBUTE} in the test context will be set to
* {@code true}.
@@ -88,15 +110,17 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi
}
if (methodDirtiesContext || (classDirtiesContext && classMode == ClassMode.AFTER_EACH_TEST_METHOD)) {
- dirtyContext(testContext);
+ HierarchyMode hierarchyMode = methodDirtiesContext ? testMethod.getAnnotation(annotationType).hierarchyMode()
+ : classDirtiesContextAnnotation.hierarchyMode();
+ dirtyContext(testContext, hierarchyMode);
}
}
/**
- * If the test class of the supplied {@link TestContext test context} is
+ * If the test class of the supplied {@linkplain TestContext test context} is
* annotated with {@link DirtiesContext @DirtiesContext}, the
- * {@link ApplicationContext application context} of the test context will
- * be {@link TestContext#markApplicationContextDirty() marked as dirty} ,
+ * {@linkplain ApplicationContext application context} of the test context will
+ * be {@linkplain TestContext#markApplicationContextDirty() marked as dirty} ,
* and the
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
* REINJECT_DEPENDENCIES_ATTRIBUTE} in the test context will be set to
@@ -107,12 +131,15 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi
Class> testClass = testContext.getTestClass();
Assert.notNull(testClass, "The test class of the supplied TestContext must not be null");
- boolean dirtiesContext = testClass.isAnnotationPresent(DirtiesContext.class);
+ final Class annotationType = DirtiesContext.class;
+
+ boolean dirtiesContext = testClass.isAnnotationPresent(annotationType);
if (logger.isDebugEnabled()) {
logger.debug("After test class: context [" + testContext + "], dirtiesContext [" + dirtiesContext + "].");
}
if (dirtiesContext) {
- dirtyContext(testContext);
+ HierarchyMode hierarchyMode = testClass.getAnnotation(annotationType).hierarchyMode();
+ dirtyContext(testContext, hierarchyMode);
}
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java
index f344ae2eeef..75c363dd188 100644
--- a/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java
+++ b/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 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.
@@ -31,6 +31,7 @@ import org.springframework.core.io.ResourceLoader;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.support.AbstractContextLoader;
+import org.springframework.util.Assert;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.GenericWebApplicationContext;
@@ -71,6 +72,12 @@ public abstract class AbstractGenericWebContextLoader extends AbstractContextLoa
*
*
* - Creates a {@link GenericWebApplicationContext} instance.
+ * - If the supplied {@code MergedContextConfiguration} references a
+ * {@linkplain MergedContextConfiguration#getParent() parent configuration},
+ * the corresponding {@link MergedContextConfiguration#getParentApplicationContext()
+ * ApplicationContext} will be retrieved and
+ * {@linkplain GenericWebApplicationContext#setParent(ApplicationContext) set as the parent}
+ * for the context created by this method.
* - Delegates to {@link #configureWebResources} to create the
* {@link MockServletContext} and set it in the {@code WebApplicationContext}.
* - Calls {@link #prepareContext} to allow for customizing the context
@@ -107,6 +114,11 @@ public abstract class AbstractGenericWebContextLoader extends AbstractContextLoa
}
GenericWebApplicationContext context = new GenericWebApplicationContext();
+
+ ApplicationContext parent = mergedConfig.getParentApplicationContext();
+ if (parent != null) {
+ context.setParent(parent);
+ }
configureWebResources(context, webMergedConfig);
prepareContext(context, webMergedConfig);
customizeBeanFactory(context.getDefaultListableBeanFactory(), webMergedConfig);
@@ -119,9 +131,20 @@ public abstract class AbstractGenericWebContextLoader extends AbstractContextLoa
}
/**
- * Configures web resources for the supplied web application context.
+ * Configures web resources for the supplied web application context (WAC).
*
- *
Implementation details:
+ *
Implementation Details
+ *
+ * If the supplied WAC has no parent or its parent is not a WAC, the
+ * supplied WAC will be configured as the Root WAC (see "Root WAC
+ * Configuration" below).
+ *
+ *
Otherwise the context hierarchy of the supplied WAC will be traversed
+ * to find the top-most WAC (i.e., the root); and the {@link ServletContext}
+ * of the Root WAC will be set as the {@code ServletContext} for the supplied
+ * WAC.
+ *
+ *
Root WAC Configuration
*
*
* - The resource base path is retrieved from the supplied
@@ -146,13 +169,33 @@ public abstract class AbstractGenericWebContextLoader extends AbstractContextLoa
protected void configureWebResources(GenericWebApplicationContext context,
WebMergedContextConfiguration webMergedConfig) {
- String resourceBasePath = webMergedConfig.getResourceBasePath();
- ResourceLoader resourceLoader = resourceBasePath.startsWith(ResourceLoader.CLASSPATH_URL_PREFIX) ? new DefaultResourceLoader()
- : new FileSystemResourceLoader();
+ ApplicationContext parent = context.getParent();
+
+ // if the WAC has no parent or the parent is not a WAC, set the WAC as
+ // the Root WAC:
+ if (parent == null || (!(parent instanceof WebApplicationContext))) {
+ String resourceBasePath = webMergedConfig.getResourceBasePath();
+ ResourceLoader resourceLoader = resourceBasePath.startsWith(ResourceLoader.CLASSPATH_URL_PREFIX) ? new DefaultResourceLoader()
+ : new FileSystemResourceLoader();
- ServletContext servletContext = new MockServletContext(resourceBasePath, resourceLoader);
- servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
- context.setServletContext(servletContext);
+ ServletContext servletContext = new MockServletContext(resourceBasePath, resourceLoader);
+ servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
+ context.setServletContext(servletContext);
+ }
+ else {
+ ServletContext servletContext = null;
+
+ // find the Root WAC
+ while (parent != null) {
+ if (parent instanceof WebApplicationContext && !(parent.getParent() instanceof WebApplicationContext)) {
+ servletContext = ((WebApplicationContext) parent).getServletContext();
+ break;
+ }
+ parent = parent.getParent();
+ }
+ Assert.state(servletContext != null, "Failed to find Root WebApplicationContext in the context hierarchy");
+ context.setServletContext(servletContext);
+ }
}
/**
diff --git a/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java
index b87c987dacd..7858d9201f7 100644
--- a/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java
+++ b/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java
@@ -69,6 +69,7 @@ public class ServletTestExecutionListener extends AbstractTestExecutionListener
* @see TestExecutionListener#prepareTestInstance(TestContext)
* @see #setUpRequestContextIfNecessary(TestContext)
*/
+ @SuppressWarnings("javadoc")
public void prepareTestInstance(TestContext testContext) throws Exception {
setUpRequestContextIfNecessary(testContext);
}
@@ -80,6 +81,7 @@ public class ServletTestExecutionListener extends AbstractTestExecutionListener
* @see TestExecutionListener#beforeTestMethod(TestContext)
* @see #setUpRequestContextIfNecessary(TestContext)
*/
+ @SuppressWarnings("javadoc")
public void beforeTestMethod(TestContext testContext) throws Exception {
setUpRequestContextIfNecessary(testContext);
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java
index 8e0e0494ad7..58e8b38607a 100644
--- a/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java
+++ b/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 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.
@@ -21,6 +21,7 @@ import java.util.Set;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.style.ToStringCreator;
+import org.springframework.test.context.CacheAwareContextLoaderDelegate;
import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.util.ObjectUtils;
@@ -77,7 +78,11 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
* @param activeProfiles the merged active bean definition profiles
* @param resourceBasePath the resource path to the root directory of the web application
* @param contextLoader the resolved {@code ContextLoader}
+ * @see #WebMergedContextConfiguration(Class, String[], Class[], Set, String[], String, ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)
+ * @deprecated as of Spring 3.2.2, use
+ * {@link #WebMergedContextConfiguration(Class, String[], Class[], Set, String[], String, ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)} instead.
*/
+ @Deprecated
public WebMergedContextConfiguration(
Class> testClass,
String[] locations,
@@ -85,7 +90,45 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
Set>> contextInitializerClasses,
String[] activeProfiles, String resourceBasePath, ContextLoader contextLoader) {
- super(testClass, locations, classes, contextInitializerClasses, activeProfiles, contextLoader);
+ this(testClass, locations, classes, contextInitializerClasses, activeProfiles, resourceBasePath, contextLoader,
+ null, null);
+ }
+
+ /**
+ * Create a new {@code WebMergedContextConfiguration} instance for the
+ * supplied test class, resource locations, annotated classes, context
+ * initializers, active profiles, resource base path, and {@code ContextLoader}.
+ *
+ *
If a {@code null} value is supplied for {@code locations},
+ * {@code classes}, or {@code activeProfiles} an empty array will
+ * be stored instead. If a {@code null} value is supplied for the
+ * {@code contextInitializerClasses} an empty set will be stored instead.
+ * If an empty value is supplied for the {@code resourceBasePath}
+ * an empty string will be used. Furthermore, active profiles will be sorted,
+ * and duplicate profiles will be removed.
+ *
+ * @param testClass the test class for which the configuration was merged
+ * @param locations the merged resource locations
+ * @param classes the merged annotated classes
+ * @param contextInitializerClasses the merged context initializer classes
+ * @param activeProfiles the merged active bean definition profiles
+ * @param resourceBasePath the resource path to the root directory of the web application
+ * @param contextLoader the resolved {@code ContextLoader}
+ * @param cacheAwareContextLoaderDelegate a cache-aware context loader
+ * delegate with which to retrieve the parent context
+ * @param parent the parent configuration or {@code null} if there is no parent
+ * @since 3.2.2
+ */
+ public WebMergedContextConfiguration(
+ Class> testClass,
+ String[] locations,
+ Class>[] classes,
+ Set>> contextInitializerClasses,
+ String[] activeProfiles, String resourceBasePath, ContextLoader contextLoader,
+ CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) {
+
+ super(testClass, locations, classes, contextInitializerClasses, activeProfiles, contextLoader,
+ cacheAwareContextLoaderDelegate, parent);
this.resourceBasePath = !StringUtils.hasText(resourceBasePath) ? "" : resourceBasePath;
}
@@ -118,8 +161,9 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
* {@linkplain #getClasses() annotated classes},
* {@linkplain #getContextInitializerClasses() context initializer classes},
* {@linkplain #getActiveProfiles() active profiles},
- * {@linkplain #getResourceBasePath() resource base path}, and the fully
- * qualified names of their {@link #getContextLoader() ContextLoaders}.
+ * {@linkplain #getResourceBasePath() resource base path},
+ * {@linkplain #getParent() parents}, and the fully qualified names of their
+ * {@link #getContextLoader() ContextLoaders}.
*/
@Override
public boolean equals(Object obj) {
@@ -141,8 +185,9 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
* {@linkplain #getLocations() locations}, {@linkplain #getClasses() annotated classes},
* {@linkplain #getContextInitializerClasses() context initializer classes},
* {@linkplain #getActiveProfiles() active profiles},
- * {@linkplain #getResourceBasePath() resource base path}, and the name of the
- * {@link #getContextLoader() ContextLoader}.
+ * {@linkplain #getResourceBasePath() resource base path}, the name of the
+ * {@link #getContextLoader() ContextLoader}, and the
+ * {@linkplain #getParent() parent configuration}.
*/
@Override
public String toString() {
@@ -154,6 +199,7 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
.append("activeProfiles", ObjectUtils.nullSafeToString(getActiveProfiles()))//
.append("resourceBasePath", getResourceBasePath())//
.append("contextLoader", nullSafeToString(getContextLoader()))//
+ .append("parent", getParent())//
.toString();
}
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
new file mode 100644
index 00000000000..eb1c39a908b
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/ContextCacheTests.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import static org.junit.Assert.*;
+import static org.springframework.test.context.SpringRunnerContextCacheTests.*;
+
+/**
+ * Integration tests for verifying proper behavior of the {@link ContextCache} in
+ * conjunction with cache keys used in {@link TestContext}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ * @see SpringRunnerContextCacheTests
+ */
+public class ContextCacheTests {
+
+ private ContextCache contextCache = new ContextCache();
+
+
+ @Before
+ public void initialCacheState() {
+ assertContextCacheStatistics(contextCache, "initial state", 0, 0, 0);
+ assertParentContextCount(0);
+ }
+
+ private void assertParentContextCount(int expected) {
+ assertEquals("parent context count", expected, contextCache.getParentContextCount());
+ }
+
+ private MergedContextConfiguration getMergedContextConfiguration(TestContext testContext) {
+ return (MergedContextConfiguration) ReflectionTestUtils.getField(testContext, "mergedContextConfiguration");
+ }
+
+ private ApplicationContext loadContext(Class> testClass) {
+ TestContext testContext = new TestContext(testClass, contextCache);
+ return testContext.getApplicationContext();
+ }
+
+ private void loadCtxAndAssertStats(Class> testClass, int expectedSize, int expectedHitCount, int expectedMissCount) {
+ assertNotNull(loadContext(testClass));
+ assertContextCacheStatistics(contextCache, testClass.getName(), expectedSize, expectedHitCount,
+ expectedMissCount);
+ }
+
+ @Test
+ public void verifyCacheKeyIsBasedOnContextLoader() {
+ loadCtxAndAssertStats(AnnotationConfigContextLoaderTestCase.class, 1, 0, 1);
+ loadCtxAndAssertStats(AnnotationConfigContextLoaderTestCase.class, 1, 1, 1);
+ loadCtxAndAssertStats(CustomAnnotationConfigContextLoaderTestCase.class, 2, 1, 2);
+ loadCtxAndAssertStats(CustomAnnotationConfigContextLoaderTestCase.class, 2, 2, 2);
+ loadCtxAndAssertStats(AnnotationConfigContextLoaderTestCase.class, 2, 3, 2);
+ loadCtxAndAssertStats(CustomAnnotationConfigContextLoaderTestCase.class, 2, 4, 2);
+ }
+
+ @Test
+ public void verifyCacheKeyIsBasedOnActiveProfiles() {
+ loadCtxAndAssertStats(FooBarProfilesTestCase.class, 1, 0, 1);
+ loadCtxAndAssertStats(FooBarProfilesTestCase.class, 1, 1, 1);
+ // Profiles {foo, bar} should hash to the same as {bar,foo}
+ loadCtxAndAssertStats(BarFooProfilesTestCase.class, 1, 2, 1);
+ loadCtxAndAssertStats(FooBarProfilesTestCase.class, 1, 3, 1);
+ loadCtxAndAssertStats(FooBarProfilesTestCase.class, 1, 4, 1);
+ loadCtxAndAssertStats(BarFooProfilesTestCase.class, 1, 5, 1);
+ }
+
+ @Test
+ public void verifyCacheBehaviorForContextHierarchies() {
+ int size = 0;
+ int hits = 0;
+ int misses = 0;
+
+ // Level 1
+ loadCtxAndAssertStats(ClassHierarchyContextHierarchyLevel1TestCase.class, ++size, hits, ++misses);
+ loadCtxAndAssertStats(ClassHierarchyContextHierarchyLevel1TestCase.class, size, ++hits, misses);
+
+ // Level 2
+ loadCtxAndAssertStats(ClassHierarchyContextHierarchyLevel2TestCase.class, ++size /* L2 */, ++hits /* L1 */,
+ ++misses /* L2 */);
+ loadCtxAndAssertStats(ClassHierarchyContextHierarchyLevel2TestCase.class, size, ++hits /* L2 */, misses);
+ loadCtxAndAssertStats(ClassHierarchyContextHierarchyLevel2TestCase.class, size, ++hits /* L2 */, misses);
+
+ // Level 3-A
+ loadCtxAndAssertStats(ClassHierarchyContextHierarchyLevel3aTestCase.class, ++size /* L3A */, ++hits /* L2 */,
+ ++misses /* L3A */);
+ loadCtxAndAssertStats(ClassHierarchyContextHierarchyLevel3aTestCase.class, size, ++hits /* L3A */, misses);
+
+ // Level 3-B
+ loadCtxAndAssertStats(ClassHierarchyContextHierarchyLevel3bTestCase.class, ++size /* L3B */, ++hits /* L2 */,
+ ++misses /* L3B */);
+ loadCtxAndAssertStats(ClassHierarchyContextHierarchyLevel3bTestCase.class, size, ++hits /* L3B */, misses);
+ }
+
+ @Test
+ public void removeContextHierarchyCacheLevel1() {
+
+ // Load Level 3-A
+ TestContext testContext3a = new TestContext(ClassHierarchyContextHierarchyLevel3aTestCase.class, contextCache);
+ testContext3a.getApplicationContext();
+ assertContextCacheStatistics(contextCache, "level 3, A", 3, 0, 3);
+ assertParentContextCount(2);
+
+ // Load Level 3-B
+ TestContext testContext3b = new TestContext(ClassHierarchyContextHierarchyLevel3bTestCase.class, contextCache);
+ testContext3b.getApplicationContext();
+ assertContextCacheStatistics(contextCache, "level 3, A and B", 4, 1, 4);
+ assertParentContextCount(2);
+
+ // Remove Level 1
+ // Should also remove Levels 2, 3-A, and 3-B, leaving nothing.
+ contextCache.remove(getMergedContextConfiguration(testContext3a).getParent().getParent(),
+ HierarchyMode.CURRENT_LEVEL);
+ assertContextCacheStatistics(contextCache, "removed level 1", 0, 1, 4);
+ assertParentContextCount(0);
+ }
+
+ @Test
+ public void removeContextHierarchyCacheLevel1WithExhaustiveMode() {
+
+ // Load Level 3-A
+ TestContext testContext3a = new TestContext(ClassHierarchyContextHierarchyLevel3aTestCase.class, contextCache);
+ testContext3a.getApplicationContext();
+ assertContextCacheStatistics(contextCache, "level 3, A", 3, 0, 3);
+ assertParentContextCount(2);
+
+ // Load Level 3-B
+ TestContext testContext3b = new TestContext(ClassHierarchyContextHierarchyLevel3bTestCase.class, contextCache);
+ testContext3b.getApplicationContext();
+ assertContextCacheStatistics(contextCache, "level 3, A and B", 4, 1, 4);
+ assertParentContextCount(2);
+
+ // Remove Level 1
+ // Should also remove Levels 2, 3-A, and 3-B, leaving nothing.
+ contextCache.remove(getMergedContextConfiguration(testContext3a).getParent().getParent(),
+ HierarchyMode.EXHAUSTIVE);
+ assertContextCacheStatistics(contextCache, "removed level 1", 0, 1, 4);
+ assertParentContextCount(0);
+ }
+
+ @Test
+ public void removeContextHierarchyCacheLevel2() {
+
+ // Load Level 3-A
+ TestContext testContext3a = new TestContext(ClassHierarchyContextHierarchyLevel3aTestCase.class, contextCache);
+ testContext3a.getApplicationContext();
+ assertContextCacheStatistics(contextCache, "level 3, A", 3, 0, 3);
+ assertParentContextCount(2);
+
+ // Load Level 3-B
+ TestContext testContext3b = new TestContext(ClassHierarchyContextHierarchyLevel3bTestCase.class, contextCache);
+ testContext3b.getApplicationContext();
+ assertContextCacheStatistics(contextCache, "level 3, A and B", 4, 1, 4);
+ assertParentContextCount(2);
+
+ // Remove Level 2
+ // Should also remove Levels 3-A and 3-B, leaving only Level 1 as a context in the
+ // cache but also removing the Level 1 hierarchy since all children have been
+ // removed.
+ contextCache.remove(getMergedContextConfiguration(testContext3a).getParent(), HierarchyMode.CURRENT_LEVEL);
+ assertContextCacheStatistics(contextCache, "removed level 2", 1, 1, 4);
+ assertParentContextCount(0);
+ }
+
+ @Test
+ public void removeContextHierarchyCacheLevel2WithExhaustiveMode() {
+
+ // Load Level 3-A
+ TestContext testContext3a = new TestContext(ClassHierarchyContextHierarchyLevel3aTestCase.class, contextCache);
+ testContext3a.getApplicationContext();
+ assertContextCacheStatistics(contextCache, "level 3, A", 3, 0, 3);
+ assertParentContextCount(2);
+
+ // Load Level 3-B
+ TestContext testContext3b = new TestContext(ClassHierarchyContextHierarchyLevel3bTestCase.class, contextCache);
+ testContext3b.getApplicationContext();
+ assertContextCacheStatistics(contextCache, "level 3, A and B", 4, 1, 4);
+ assertParentContextCount(2);
+
+ // Remove Level 2
+ // Should wipe the cache
+ contextCache.remove(getMergedContextConfiguration(testContext3a).getParent(), HierarchyMode.EXHAUSTIVE);
+ assertContextCacheStatistics(contextCache, "removed level 2", 0, 1, 4);
+ assertParentContextCount(0);
+ }
+
+ @Test
+ public void removeContextHierarchyCacheLevel3Then2() {
+
+ // Load Level 3-A
+ TestContext testContext3a = new TestContext(ClassHierarchyContextHierarchyLevel3aTestCase.class, contextCache);
+ testContext3a.getApplicationContext();
+ assertContextCacheStatistics(contextCache, "level 3, A", 3, 0, 3);
+ assertParentContextCount(2);
+
+ // Load Level 3-B
+ TestContext testContext3b = new TestContext(ClassHierarchyContextHierarchyLevel3bTestCase.class, contextCache);
+ testContext3b.getApplicationContext();
+ assertContextCacheStatistics(contextCache, "level 3, A and B", 4, 1, 4);
+ assertParentContextCount(2);
+
+ // Remove Level 3-A
+ contextCache.remove(getMergedContextConfiguration(testContext3a), HierarchyMode.CURRENT_LEVEL);
+ assertContextCacheStatistics(contextCache, "removed level 3-A", 3, 1, 4);
+ assertParentContextCount(2);
+
+ // Remove Level 2
+ // Should also remove Level 3-B, leaving only Level 1.
+ contextCache.remove(getMergedContextConfiguration(testContext3b).getParent(), HierarchyMode.CURRENT_LEVEL);
+ assertContextCacheStatistics(contextCache, "removed level 2", 1, 1, 4);
+ assertParentContextCount(0);
+ }
+
+ @Test
+ public void removeContextHierarchyCacheLevel3Then2WithExhaustiveMode() {
+
+ // Load Level 3-A
+ TestContext testContext3a = new TestContext(ClassHierarchyContextHierarchyLevel3aTestCase.class, contextCache);
+ testContext3a.getApplicationContext();
+ assertContextCacheStatistics(contextCache, "level 3, A", 3, 0, 3);
+ assertParentContextCount(2);
+
+ // Load Level 3-B
+ TestContext testContext3b = new TestContext(ClassHierarchyContextHierarchyLevel3bTestCase.class, contextCache);
+ testContext3b.getApplicationContext();
+ assertContextCacheStatistics(contextCache, "level 3, A and B", 4, 1, 4);
+ assertParentContextCount(2);
+
+ // Remove Level 3-A
+ // Should wipe the cache.
+ contextCache.remove(getMergedContextConfiguration(testContext3a), HierarchyMode.EXHAUSTIVE);
+ assertContextCacheStatistics(contextCache, "removed level 3-A", 0, 1, 4);
+ assertParentContextCount(0);
+
+ // Remove Level 2
+ // Should not actually do anything since the cache was cleared in the
+ // previous step. So the stats should remain the same.
+ contextCache.remove(getMergedContextConfiguration(testContext3b).getParent(), HierarchyMode.EXHAUSTIVE);
+ assertContextCacheStatistics(contextCache, "removed level 2", 0, 1, 4);
+ assertParentContextCount(0);
+ }
+
+
+ @Configuration
+ static class Config {
+ }
+
+ @ContextConfiguration(classes = Config.class, loader = AnnotationConfigContextLoader.class)
+ private static class AnnotationConfigContextLoaderTestCase {
+ }
+
+ @ContextConfiguration(classes = Config.class, loader = CustomAnnotationConfigContextLoader.class)
+ private static class CustomAnnotationConfigContextLoaderTestCase {
+ }
+
+ private static class CustomAnnotationConfigContextLoader extends AnnotationConfigContextLoader {
+ }
+
+ @ActiveProfiles({ "foo", "bar" })
+ @ContextConfiguration(classes = Config.class, loader = AnnotationConfigContextLoader.class)
+ private static class FooBarProfilesTestCase {
+ }
+
+ @ActiveProfiles({ "bar", "foo" })
+ @ContextConfiguration(classes = Config.class, loader = AnnotationConfigContextLoader.class)
+ private static class BarFooProfilesTestCase {
+ }
+
+ @ContextHierarchy({ @ContextConfiguration })
+ private static class ClassHierarchyContextHierarchyLevel1TestCase {
+
+ @Configuration
+ static class Level1Config {
+
+ }
+ }
+
+ @ContextHierarchy({ @ContextConfiguration })
+ private static class ClassHierarchyContextHierarchyLevel2TestCase extends
+ ClassHierarchyContextHierarchyLevel1TestCase {
+
+ @Configuration
+ static class Level2Config {
+
+ }
+ }
+
+ @ContextHierarchy({ @ContextConfiguration })
+ private static class ClassHierarchyContextHierarchyLevel3aTestCase extends
+ ClassHierarchyContextHierarchyLevel2TestCase {
+
+ @Configuration
+ static class Level3aConfig {
+
+ }
+ }
+
+ @ContextHierarchy({ @ContextConfiguration })
+ private static class ClassHierarchyContextHierarchyLevel3bTestCase extends
+ ClassHierarchyContextHierarchyLevel2TestCase {
+
+ @Configuration
+ static class Level3bConfig {
+
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextHierarchyDirtiesContextTests.java b/spring-test/src/test/java/org/springframework/test/context/ContextHierarchyDirtiesContextTests.java
new file mode 100644
index 00000000000..3026e3a0a9b
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/ContextHierarchyDirtiesContextTests.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context;
+
+import org.junit.After;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.JUnitCore;
+import org.junit.runner.Result;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * Integration tests that verify proper behavior of {@link DirtiesContext @DirtiesContext}
+ * in conjunction with context hierarchies configured via {@link ContextHierarchy @ContextHierarchy}.
+ *
+ * @author Sam Brannen
+ * @author Tadaya Tsuyukubo
+ * @since 3.2.2
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class ContextHierarchyDirtiesContextTests {
+
+ private static ApplicationContext context;
+
+
+ @After
+ public void cleanUp() {
+ ContextHierarchyDirtiesContextTests.context = null;
+ }
+
+ @Test
+ public void classLevelDirtiesContextWithCurrentLevelHierarchyMode() {
+ runTestAndVerifyHierarchies(ClassLevelDirtiesContextWithCurrentLevelModeTestCase.class, true, true, false);
+ }
+
+ @Test
+ public void classLevelDirtiesContextWithExhaustiveHierarchyMode() {
+ runTestAndVerifyHierarchies(ClassLevelDirtiesContextWithExhaustiveModeTestCase.class, false, false, false);
+ }
+
+ @Test
+ public void methodLevelDirtiesContextWithCurrentLevelHierarchyMode() {
+ runTestAndVerifyHierarchies(MethodLevelDirtiesContextWithCurrentLevelModeTestCase.class, true, true, false);
+ }
+
+ @Test
+ public void methodLevelDirtiesContextWithExhaustiveHierarchyMode() {
+ runTestAndVerifyHierarchies(MethodLevelDirtiesContextWithExhaustiveModeTestCase.class, false, false, false);
+ }
+
+ private void runTestAndVerifyHierarchies(Class extends FooTestCase> testClass, boolean isFooContextActive,
+ boolean isBarContextActive, boolean isBazContextActive) {
+
+ JUnitCore jUnitCore = new JUnitCore();
+ Result result = jUnitCore.run(testClass);
+ assertTrue("all tests passed", result.wasSuccessful());
+
+ assertThat(ContextHierarchyDirtiesContextTests.context, notNullValue());
+
+ ConfigurableApplicationContext bazContext = (ConfigurableApplicationContext) ContextHierarchyDirtiesContextTests.context;
+ assertEquals("baz", bazContext.getBean("bean", String.class));
+ assertThat("bazContext#isActive()", bazContext.isActive(), is(isBazContextActive));
+
+ ConfigurableApplicationContext barContext = (ConfigurableApplicationContext) bazContext.getParent();
+ assertThat(barContext, notNullValue());
+ assertEquals("bar", barContext.getBean("bean", String.class));
+ assertThat("barContext#isActive()", barContext.isActive(), is(isBarContextActive));
+
+ ConfigurableApplicationContext fooContext = (ConfigurableApplicationContext) barContext.getParent();
+ assertThat(fooContext, notNullValue());
+ assertEquals("foo", fooContext.getBean("bean", String.class));
+ assertThat("fooContext#isActive()", fooContext.isActive(), is(isFooContextActive));
+ }
+
+
+ // -------------------------------------------------------------------------
+
+ @RunWith(SpringJUnit4ClassRunner.class)
+ @ContextHierarchy(@ContextConfiguration(name = "foo"))
+ static abstract class FooTestCase implements ApplicationContextAware {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public String bean() {
+ return "foo";
+ }
+ }
+
+
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ ContextHierarchyDirtiesContextTests.context = applicationContext;
+ }
+ }
+
+ @ContextHierarchy(@ContextConfiguration(name = "bar"))
+ static abstract class BarTestCase extends FooTestCase {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public String bean() {
+ return "bar";
+ }
+ }
+ }
+
+ @ContextHierarchy(@ContextConfiguration(name = "baz"))
+ static abstract class BazTestCase extends BarTestCase {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public String bean() {
+ return "baz";
+ }
+ }
+ }
+
+ // -------------------------------------------------------------------------
+
+ /**
+ * {@link DirtiesContext} is declared at the class level, without specifying
+ * the {@link DirtiesContext.HierarchyMode}.
+ * After running this test class, the context cache should be exhaustively
+ * cleared beginning from the current context hierarchy, upwards to the highest
+ * parent context, and then back down through all subhierarchies of the parent
+ * context.
+ */
+ @DirtiesContext
+ public static class ClassLevelDirtiesContextWithExhaustiveModeTestCase extends BazTestCase {
+
+ @Test
+ public void test() {
+ }
+ }
+
+ /**
+ * {@link DirtiesContext} is declared at the class level, specifying the
+ * {@link DirtiesContext.HierarchyMode#CURRENT_LEVEL CURRENT_LEVEL} hierarchy mode.
+ *
After running this test class, the context cache should be cleared
+ * beginning from the current context hierarchy and down through all subhierarchies.
+ */
+ @DirtiesContext(hierarchyMode = HierarchyMode.CURRENT_LEVEL)
+ public static class ClassLevelDirtiesContextWithCurrentLevelModeTestCase extends BazTestCase {
+
+ @Test
+ public void test() {
+ }
+ }
+
+ /**
+ * {@link DirtiesContext} is declared at the method level, without specifying
+ * the {@link DirtiesContext.HierarchyMode}.
+ *
After running this test class, the context cache should be exhaustively
+ * cleared beginning from the current context hierarchy, upwards to the highest
+ * parent context, and then back down through all subhierarchies of the parent
+ * context.
+ */
+ public static class MethodLevelDirtiesContextWithExhaustiveModeTestCase extends BazTestCase {
+
+ @Test
+ @DirtiesContext
+ public void test() {
+ }
+ }
+
+ /**
+ * {@link DirtiesContext} is declared at the method level, specifying the
+ * {@link DirtiesContext.HierarchyMode#CURRENT_LEVEL CURRENT_LEVEL} hierarchy mode.
+ *
After running this test class, the context cache should be cleared
+ * beginning from the current context hierarchy and down through all subhierarchies.
+ */
+ public static class MethodLevelDirtiesContextWithCurrentLevelModeTestCase extends BazTestCase {
+
+ @Test
+ @DirtiesContext(hierarchyMode = HierarchyMode.CURRENT_LEVEL)
+ public void test() {
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests.java
index fdfa4eb4ac8..ecedda4ddf9 100644
--- a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 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,13 +16,16 @@
package org.springframework.test.context;
+import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.springframework.test.context.ContextLoaderUtils.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import org.junit.Test;
@@ -105,6 +108,233 @@ public class ContextLoaderUtilsTests {
assertEquals(expectedInitializerClasses, mergedConfig.getContextInitializerClasses());
}
+ private void debugConfigAttributes(List configAttributesList) {
+ // for (ContextConfigurationAttributes configAttributes : configAttributesList) {
+ // System.err.println(configAttributes);
+ // }
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void resolveContextHierarchyAttributesForSingleTestClassWithContextConfigurationAndContextHierarchy() {
+ resolveContextHierarchyAttributes(SingleTestClassWithContextConfigurationAndContextHierarchy.class);
+ }
+
+ @Test
+ public void resolveContextHierarchyAttributesForSingleTestClassWithImplicitSingleLevelContextHierarchy() {
+ List> hierarchyAttributes = resolveContextHierarchyAttributes(BareAnnotations.class);
+ assertEquals(1, hierarchyAttributes.size());
+ List configAttributesList = hierarchyAttributes.get(0);
+ assertEquals(1, configAttributesList.size());
+ debugConfigAttributes(configAttributesList);
+ }
+
+ @Test
+ public void resolveContextHierarchyAttributesForSingleTestClassWithSingleLevelContextHierarchy() {
+ List> hierarchyAttributes = resolveContextHierarchyAttributes(SingleTestClassWithSingleLevelContextHierarchy.class);
+ assertEquals(1, hierarchyAttributes.size());
+ List configAttributesList = hierarchyAttributes.get(0);
+ assertEquals(1, configAttributesList.size());
+ debugConfigAttributes(configAttributesList);
+ }
+
+ @Test
+ public void resolveContextHierarchyAttributesForSingleTestClassWithTripleLevelContextHierarchy() {
+ List> hierarchyAttributes = resolveContextHierarchyAttributes(SingleTestClassWithTripleLevelContextHierarchy.class);
+ assertEquals(1, hierarchyAttributes.size());
+ List configAttributesList = hierarchyAttributes.get(0);
+ assertEquals(3, configAttributesList.size());
+ debugConfigAttributes(configAttributesList);
+ }
+
+ @Test
+ public void resolveContextHierarchyAttributesForTestClassHierarchyWithSingleLevelContextHierarchies() {
+ List> hierarchyAttributes = resolveContextHierarchyAttributes(TestClass3WithSingleLevelContextHierarchy.class);
+ assertEquals(3, hierarchyAttributes.size());
+
+ List configAttributesListClassLevel1 = hierarchyAttributes.get(0);
+ debugConfigAttributes(configAttributesListClassLevel1);
+ assertEquals(1, configAttributesListClassLevel1.size());
+ assertThat(configAttributesListClassLevel1.get(0).getLocations()[0], equalTo("one.xml"));
+
+ List configAttributesListClassLevel2 = hierarchyAttributes.get(1);
+ debugConfigAttributes(configAttributesListClassLevel2);
+ assertEquals(1, configAttributesListClassLevel2.size());
+ assertArrayEquals(new String[] { "two-A.xml", "two-B.xml" },
+ configAttributesListClassLevel2.get(0).getLocations());
+
+ List configAttributesListClassLevel3 = hierarchyAttributes.get(2);
+ debugConfigAttributes(configAttributesListClassLevel3);
+ assertEquals(1, configAttributesListClassLevel3.size());
+ assertThat(configAttributesListClassLevel3.get(0).getLocations()[0], equalTo("three.xml"));
+ }
+
+ @Test
+ public void resolveContextHierarchyAttributesForTestClassHierarchyWithBareContextConfigurationInSubclass() {
+ List> hierarchyAttributes = resolveContextHierarchyAttributes(TestClass2WithBareContextConfigurationInSubclass.class);
+ assertEquals(2, hierarchyAttributes.size());
+
+ List configAttributesListClassLevel1 = hierarchyAttributes.get(0);
+ debugConfigAttributes(configAttributesListClassLevel1);
+ assertEquals(1, configAttributesListClassLevel1.size());
+ assertThat(configAttributesListClassLevel1.get(0).getLocations()[0], equalTo("one.xml"));
+
+ List configAttributesListClassLevel2 = hierarchyAttributes.get(1);
+ debugConfigAttributes(configAttributesListClassLevel2);
+ assertEquals(1, configAttributesListClassLevel2.size());
+ assertThat(configAttributesListClassLevel2.get(0).getLocations()[0], equalTo("two.xml"));
+ }
+
+ @Test
+ public void resolveContextHierarchyAttributesForTestClassHierarchyWithBareContextConfigurationInSuperclass() {
+ List> hierarchyAttributes = resolveContextHierarchyAttributes(TestClass2WithBareContextConfigurationInSuperclass.class);
+ assertEquals(2, hierarchyAttributes.size());
+
+ List configAttributesListClassLevel1 = hierarchyAttributes.get(0);
+ debugConfigAttributes(configAttributesListClassLevel1);
+ assertEquals(1, configAttributesListClassLevel1.size());
+ assertThat(configAttributesListClassLevel1.get(0).getLocations()[0], equalTo("one.xml"));
+
+ List configAttributesListClassLevel2 = hierarchyAttributes.get(1);
+ debugConfigAttributes(configAttributesListClassLevel2);
+ assertEquals(1, configAttributesListClassLevel2.size());
+ assertThat(configAttributesListClassLevel2.get(0).getLocations()[0], equalTo("two.xml"));
+ }
+
+ @Test
+ public void resolveContextHierarchyAttributesForTestClassHierarchyWithMultiLevelContextHierarchies() {
+ List> hierarchyAttributes = resolveContextHierarchyAttributes(TestClass3WithMultiLevelContextHierarchy.class);
+ assertEquals(3, hierarchyAttributes.size());
+
+ List configAttributesListClassLevel1 = hierarchyAttributes.get(0);
+ debugConfigAttributes(configAttributesListClassLevel1);
+ assertEquals(2, configAttributesListClassLevel1.size());
+ assertThat(configAttributesListClassLevel1.get(0).getLocations()[0], equalTo("1-A.xml"));
+ assertThat(configAttributesListClassLevel1.get(1).getLocations()[0], equalTo("1-B.xml"));
+
+ List configAttributesListClassLevel2 = hierarchyAttributes.get(1);
+ debugConfigAttributes(configAttributesListClassLevel2);
+ assertEquals(2, configAttributesListClassLevel2.size());
+ assertThat(configAttributesListClassLevel2.get(0).getLocations()[0], equalTo("2-A.xml"));
+ assertThat(configAttributesListClassLevel2.get(1).getLocations()[0], equalTo("2-B.xml"));
+
+ List configAttributesListClassLevel3 = hierarchyAttributes.get(2);
+ debugConfigAttributes(configAttributesListClassLevel3);
+ assertEquals(3, configAttributesListClassLevel3.size());
+ assertThat(configAttributesListClassLevel3.get(0).getLocations()[0], equalTo("3-A.xml"));
+ assertThat(configAttributesListClassLevel3.get(1).getLocations()[0], equalTo("3-B.xml"));
+ assertThat(configAttributesListClassLevel3.get(2).getLocations()[0], equalTo("3-C.xml"));
+ }
+
+ private void assertContextConfigEntriesAreNotUnique(Class> testClass) {
+ try {
+ resolveContextHierarchyAttributes(testClass);
+ fail("Should throw an IllegalStateException");
+ }
+ catch (IllegalStateException e) {
+ String msg = String.format(
+ "The @ContextConfiguration elements configured via @ContextHierarchy in test class [%s] must define unique contexts to load.",
+ testClass.getName());
+ assertEquals(msg, e.getMessage());
+ }
+ }
+
+ @Test
+ public void resolveContextHierarchyAttributesForSingleTestClassWithMultiLevelContextHierarchyWithEmptyContextConfig() {
+ assertContextConfigEntriesAreNotUnique(SingleTestClassWithMultiLevelContextHierarchyWithEmptyContextConfig.class);
+ }
+
+ @Test
+ public void resolveContextHierarchyAttributesForSingleTestClassWithMultiLevelContextHierarchyWithDuplicatedContextConfig() {
+ assertContextConfigEntriesAreNotUnique(SingleTestClassWithMultiLevelContextHierarchyWithDuplicatedContextConfig.class);
+ }
+
+ @Test
+ public void buildContextHierarchyMapForTestClassHierarchyWithMultiLevelContextHierarchies() {
+ Map> map = buildContextHierarchyMap(TestClass3WithMultiLevelContextHierarchy.class);
+
+ assertThat(map.size(), is(3));
+ assertThat(map.keySet(), hasItems("alpha", "beta", "gamma"));
+
+ List alphaConfig = map.get("alpha");
+ assertThat(alphaConfig.size(), is(3));
+ assertThat(alphaConfig.get(0).getLocations()[0], is("1-A.xml"));
+ assertThat(alphaConfig.get(1).getLocations()[0], is("2-A.xml"));
+ assertThat(alphaConfig.get(2).getLocations()[0], is("3-A.xml"));
+
+ List betaConfig = map.get("beta");
+ assertThat(betaConfig.size(), is(3));
+ assertThat(betaConfig.get(0).getLocations()[0], is("1-B.xml"));
+ assertThat(betaConfig.get(1).getLocations()[0], is("2-B.xml"));
+ assertThat(betaConfig.get(2).getLocations()[0], is("3-B.xml"));
+
+ List gammaConfig = map.get("gamma");
+ assertThat(gammaConfig.size(), is(1));
+ assertThat(gammaConfig.get(0).getLocations()[0], is("3-C.xml"));
+ }
+
+ @Test
+ public void buildContextHierarchyMapForTestClassHierarchyWithMultiLevelContextHierarchiesAndUnnamedConfig() {
+ Map> map = buildContextHierarchyMap(TestClass3WithMultiLevelContextHierarchyAndUnnamedConfig.class);
+
+ String level1 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 1;
+ String level2 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 2;
+ String level3 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 3;
+ String level4 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 4;
+ String level5 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 5;
+ String level6 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 6;
+ String level7 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 7;
+
+ assertThat(map.size(), is(7));
+ assertThat(map.keySet(), hasItems(level1, level2, level3, level4, level5, level6, level7));
+
+ List level1Config = map.get(level1);
+ assertThat(level1Config.size(), is(1));
+ assertThat(level1Config.get(0).getLocations()[0], is("1-A.xml"));
+
+ List level2Config = map.get(level2);
+ assertThat(level2Config.size(), is(1));
+ assertThat(level2Config.get(0).getLocations()[0], is("1-B.xml"));
+
+ List level3Config = map.get(level3);
+ assertThat(level3Config.size(), is(1));
+ assertThat(level3Config.get(0).getLocations()[0], is("2-A.xml"));
+
+ // ...
+
+ List level7Config = map.get(level7);
+ assertThat(level7Config.size(), is(1));
+ assertThat(level7Config.get(0).getLocations()[0], is("3-C.xml"));
+ }
+
+ @Test
+ public void buildContextHierarchyMapForTestClassHierarchyWithMultiLevelContextHierarchiesAndPartiallyNamedConfig() {
+ Map> map = buildContextHierarchyMap(TestClass2WithMultiLevelContextHierarchyAndPartiallyNamedConfig.class);
+
+ String level1 = "parent";
+ String level2 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 2;
+ String level3 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 3;
+
+ assertThat(map.size(), is(3));
+ assertThat(map.keySet(), hasItems(level1, level2, level3));
+ Iterator levels = map.keySet().iterator();
+ assertThat(levels.next(), is(level1));
+ assertThat(levels.next(), is(level2));
+ assertThat(levels.next(), is(level3));
+
+ List level1Config = map.get(level1);
+ assertThat(level1Config.size(), is(2));
+ assertThat(level1Config.get(0).getLocations()[0], is("1-A.xml"));
+ assertThat(level1Config.get(1).getLocations()[0], is("2-A.xml"));
+
+ List level2Config = map.get(level2);
+ assertThat(level2Config.size(), is(1));
+ assertThat(level2Config.get(0).getLocations()[0], is("1-B.xml"));
+
+ List level3Config = map.get(level3);
+ assertThat(level3Config.size(), is(1));
+ assertThat(level3Config.get(0).getLocations()[0], is("2-C.xml"));
+ }
+
@Test(expected = IllegalStateException.class)
public void resolveConfigAttributesWithConflictingLocations() {
resolveContextConfigurationAttributes(ConflictingLocations.class);
@@ -155,13 +385,13 @@ public class ContextLoaderUtilsTests {
@Test(expected = IllegalArgumentException.class)
public void buildMergedConfigWithoutAnnotation() {
- buildMergedContextConfiguration(Enigma.class, null);
+ buildMergedContextConfiguration(Enigma.class, null, null);
}
@Test
public void buildMergedConfigWithBareAnnotations() {
Class testClass = BareAnnotations.class;
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null);
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
assertMergedConfig(
mergedConfig,
@@ -173,7 +403,7 @@ public class ContextLoaderUtilsTests {
@Test
public void buildMergedConfigWithLocalAnnotationAndLocations() {
Class> testClass = LocationsFoo.class;
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null);
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
assertMergedConfig(mergedConfig, testClass, new String[] { "classpath:/foo.xml" }, EMPTY_CLASS_ARRAY,
DelegatingSmartContextLoader.class);
@@ -182,7 +412,7 @@ public class ContextLoaderUtilsTests {
@Test
public void buildMergedConfigWithLocalAnnotationAndClasses() {
Class> testClass = ClassesFoo.class;
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null);
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, new Class>[] { FooConfig.class },
DelegatingSmartContextLoader.class);
@@ -193,7 +423,7 @@ public class ContextLoaderUtilsTests {
Class> testClass = LocationsFoo.class;
Class extends ContextLoader> expectedContextLoaderClass = GenericPropertiesContextLoader.class;
MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass,
- expectedContextLoaderClass.getName());
+ expectedContextLoaderClass.getName(), null);
assertMergedConfig(mergedConfig, testClass, new String[] { "classpath:/foo.xml" }, EMPTY_CLASS_ARRAY,
expectedContextLoaderClass);
@@ -204,7 +434,7 @@ public class ContextLoaderUtilsTests {
Class> testClass = ClassesFoo.class;
Class extends ContextLoader> expectedContextLoaderClass = GenericPropertiesContextLoader.class;
MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass,
- expectedContextLoaderClass.getName());
+ expectedContextLoaderClass.getName(), null);
assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, new Class>[] { FooConfig.class },
expectedContextLoaderClass);
@@ -215,7 +445,7 @@ public class ContextLoaderUtilsTests {
Class> testClass = LocationsBar.class;
String[] expectedLocations = new String[] { "/foo.xml", "/bar.xml" };
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null);
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
assertMergedConfig(mergedConfig, testClass, expectedLocations, EMPTY_CLASS_ARRAY,
AnnotationConfigContextLoader.class);
}
@@ -225,7 +455,7 @@ public class ContextLoaderUtilsTests {
Class> testClass = ClassesBar.class;
Class>[] expectedClasses = new Class>[] { FooConfig.class, BarConfig.class };
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null);
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses,
AnnotationConfigContextLoader.class);
}
@@ -235,7 +465,7 @@ public class ContextLoaderUtilsTests {
Class> testClass = OverriddenLocationsBar.class;
String[] expectedLocations = new String[] { "/bar.xml" };
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null);
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
assertMergedConfig(mergedConfig, testClass, expectedLocations, EMPTY_CLASS_ARRAY,
AnnotationConfigContextLoader.class);
}
@@ -245,7 +475,7 @@ public class ContextLoaderUtilsTests {
Class> testClass = OverriddenClassesBar.class;
Class>[] expectedClasses = new Class>[] { BarConfig.class };
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null);
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses,
AnnotationConfigContextLoader.class);
}
@@ -258,7 +488,7 @@ public class ContextLoaderUtilsTests {
= new HashSet>>();
expectedInitializerClasses.add(FooInitializer.class);
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null);
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses,
DelegatingSmartContextLoader.class);
}
@@ -272,7 +502,7 @@ public class ContextLoaderUtilsTests {
expectedInitializerClasses.add(FooInitializer.class);
expectedInitializerClasses.add(BarInitializer.class);
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null);
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses,
DelegatingSmartContextLoader.class);
}
@@ -285,7 +515,7 @@ public class ContextLoaderUtilsTests {
= new HashSet>>();
expectedInitializerClasses.add(BarInitializer.class);
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null);
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses,
DelegatingSmartContextLoader.class);
}
@@ -298,7 +528,7 @@ public class ContextLoaderUtilsTests {
= new HashSet>>();
expectedInitializerClasses.add(BarInitializer.class);
- MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null);
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses,
DelegatingSmartContextLoader.class);
}
@@ -377,6 +607,8 @@ public class ContextLoaderUtilsTests {
}
+ // -------------------------------------------------------------------------
+
private static class Enigma {
}
@@ -475,4 +707,140 @@ public class ContextLoaderUtilsTests {
private static class OverriddenInitializersAndClassesBar extends InitializersFoo {
}
+ @ContextConfiguration("foo.xml")
+ @ContextHierarchy(@ContextConfiguration("bar.xml"))
+ private static class SingleTestClassWithContextConfigurationAndContextHierarchy {
+ }
+
+ @ContextHierarchy(@ContextConfiguration("A.xml"))
+ private static class SingleTestClassWithSingleLevelContextHierarchy {
+ }
+
+ @ContextHierarchy({//
+ //
+ @ContextConfiguration("A.xml"),//
+ @ContextConfiguration("B.xml"),//
+ @ContextConfiguration("C.xml") //
+ })
+ private static class SingleTestClassWithTripleLevelContextHierarchy {
+ }
+
+ @ContextHierarchy(@ContextConfiguration("one.xml"))
+ private static class TestClass1WithSingleLevelContextHierarchy {
+ }
+
+ @ContextHierarchy(@ContextConfiguration({ "two-A.xml", "two-B.xml" }))
+ private static class TestClass2WithSingleLevelContextHierarchy extends TestClass1WithSingleLevelContextHierarchy {
+ }
+
+ @ContextHierarchy(@ContextConfiguration("three.xml"))
+ private static class TestClass3WithSingleLevelContextHierarchy extends TestClass2WithSingleLevelContextHierarchy {
+ }
+
+ @ContextConfiguration("one.xml")
+ private static class TestClass1WithBareContextConfigurationInSuperclass {
+ }
+
+ @ContextHierarchy(@ContextConfiguration("two.xml"))
+ private static class TestClass2WithBareContextConfigurationInSuperclass extends
+ TestClass1WithBareContextConfigurationInSuperclass {
+ }
+
+ @ContextHierarchy(@ContextConfiguration("one.xml"))
+ private static class TestClass1WithBareContextConfigurationInSubclass {
+ }
+
+ @ContextConfiguration("two.xml")
+ private static class TestClass2WithBareContextConfigurationInSubclass extends
+ TestClass1WithBareContextConfigurationInSuperclass {
+ }
+
+ @ContextHierarchy({//
+ //
+ @ContextConfiguration(locations = "1-A.xml", name = "alpha"),//
+ @ContextConfiguration(locations = "1-B.xml", name = "beta") //
+ })
+ private static class TestClass1WithMultiLevelContextHierarchy {
+ }
+
+ @ContextHierarchy({//
+ //
+ @ContextConfiguration(locations = "2-A.xml", name = "alpha"),//
+ @ContextConfiguration(locations = "2-B.xml", name = "beta") //
+ })
+ private static class TestClass2WithMultiLevelContextHierarchy extends TestClass1WithMultiLevelContextHierarchy {
+ }
+
+ @ContextHierarchy({//
+ //
+ @ContextConfiguration(locations = "3-A.xml", name = "alpha"),//
+ @ContextConfiguration(locations = "3-B.xml", name = "beta"),//
+ @ContextConfiguration(locations = "3-C.xml", name = "gamma") //
+ })
+ private static class TestClass3WithMultiLevelContextHierarchy extends TestClass2WithMultiLevelContextHierarchy {
+ }
+
+ @ContextHierarchy({//
+ //
+ @ContextConfiguration(locations = "1-A.xml"),//
+ @ContextConfiguration(locations = "1-B.xml") //
+ })
+ private static class TestClass1WithMultiLevelContextHierarchyAndUnnamedConfig {
+ }
+
+ @ContextHierarchy({//
+ //
+ @ContextConfiguration(locations = "2-A.xml"),//
+ @ContextConfiguration(locations = "2-B.xml") //
+ })
+ private static class TestClass2WithMultiLevelContextHierarchyAndUnnamedConfig extends
+ TestClass1WithMultiLevelContextHierarchyAndUnnamedConfig {
+ }
+
+ @ContextHierarchy({//
+ //
+ @ContextConfiguration(locations = "3-A.xml"),//
+ @ContextConfiguration(locations = "3-B.xml"),//
+ @ContextConfiguration(locations = "3-C.xml") //
+ })
+ private static class TestClass3WithMultiLevelContextHierarchyAndUnnamedConfig extends
+ TestClass2WithMultiLevelContextHierarchyAndUnnamedConfig {
+ }
+
+ @ContextHierarchy({//
+ //
+ @ContextConfiguration(locations = "1-A.xml", name = "parent"),//
+ @ContextConfiguration(locations = "1-B.xml") //
+ })
+ private static class TestClass1WithMultiLevelContextHierarchyAndPartiallyNamedConfig {
+ }
+
+ @ContextHierarchy({//
+ //
+ @ContextConfiguration(locations = "2-A.xml", name = "parent"),//
+ @ContextConfiguration(locations = "2-C.xml") //
+ })
+ private static class TestClass2WithMultiLevelContextHierarchyAndPartiallyNamedConfig extends
+ TestClass1WithMultiLevelContextHierarchyAndPartiallyNamedConfig {
+ }
+
+ @ContextHierarchy({
+ //
+ @ContextConfiguration,//
+ @ContextConfiguration //
+ })
+ private static class SingleTestClassWithMultiLevelContextHierarchyWithEmptyContextConfig {
+ }
+
+ @ContextHierarchy({
+ //
+ @ContextConfiguration("foo.xml"),//
+ @ContextConfiguration(classes = BarConfig.class),// duplicate!
+ @ContextConfiguration("baz.xml"),//
+ @ContextConfiguration(classes = BarConfig.class),// duplicate!
+ @ContextConfiguration(loader = AnnotationConfigContextLoader.class) //
+ })
+ private static class SingleTestClassWithMultiLevelContextHierarchyWithDuplicatedContextConfig {
+ }
+
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/MergedContextConfigurationTests.java b/spring-test/src/test/java/org/springframework/test/context/MergedContextConfigurationTests.java
index 35b64fa432b..863fd9da868 100644
--- a/spring-test/src/test/java/org/springframework/test/context/MergedContextConfigurationTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/MergedContextConfigurationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 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.
@@ -76,7 +76,7 @@ public class MergedContextConfigurationTests {
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, new AnnotationConfigContextLoader());
- assertFalse(mergedConfig1.hashCode() == mergedConfig2.hashCode());
+ assertNotEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
}
@Test
@@ -97,7 +97,7 @@ public class MergedContextConfigurationTests {
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), locations2,
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
- assertFalse(mergedConfig1.hashCode() == mergedConfig2.hashCode());
+ assertNotEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
}
@Test
@@ -118,7 +118,7 @@ public class MergedContextConfigurationTests {
classes1, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
classes2, EMPTY_STRING_ARRAY, loader);
- assertFalse(mergedConfig1.hashCode() == mergedConfig2.hashCode());
+ assertNotEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
}
@Test
@@ -161,7 +161,7 @@ public class MergedContextConfigurationTests {
EMPTY_CLASS_ARRAY, activeProfiles1, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, activeProfiles2, loader);
- assertFalse(mergedConfig1.hashCode() == mergedConfig2.hashCode());
+ assertNotEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
}
@Test
@@ -197,15 +197,47 @@ public class MergedContextConfigurationTests {
EMPTY_CLASS_ARRAY, initializerClasses1, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, initializerClasses2, EMPTY_STRING_ARRAY, loader);
- assertFalse(mergedConfig1.hashCode() == mergedConfig2.hashCode());
+ assertNotEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
+ }
+
+ /**
+ * @since 3.2.2
+ */
+ @Test
+ public void hashCodeWithSameParent() {
+ MergedContextConfiguration parent = new MergedContextConfiguration(getClass(), new String[] { "foo", "bar}" },
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, loader, null, parent);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, loader, null, parent);
+ assertEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
+ }
+
+ /**
+ * @since 3.2.2
+ */
+ @Test
+ public void hashCodeWithDifferentParents() {
+ MergedContextConfiguration parent1 = new MergedContextConfiguration(getClass(), new String[] { "foo", "bar}" },
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+ MergedContextConfiguration parent2 = new MergedContextConfiguration(getClass(), new String[] { "baz", "quux" },
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, loader, null, parent1);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, loader, null, parent2);
+ assertNotEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
}
@Test
public void equalsBasics() {
MergedContextConfiguration mergedConfig = new MergedContextConfiguration(null, null, null, null, null);
- assertTrue(mergedConfig.equals(mergedConfig));
- assertFalse(mergedConfig.equals(null));
- assertFalse(mergedConfig.equals(new Integer(1)));
+ assertEquals(mergedConfig, mergedConfig);
+ assertNotEquals(mergedConfig, null);
+ assertNotEquals(mergedConfig, new Integer(1));
}
@Test
@@ -237,8 +269,8 @@ public class MergedContextConfigurationTests {
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, new AnnotationConfigContextLoader());
- assertFalse(mergedConfig1.equals(mergedConfig2));
- assertFalse(mergedConfig2.equals(mergedConfig1));
+ assertNotEquals(mergedConfig1, mergedConfig2);
+ assertNotEquals(mergedConfig2, mergedConfig1);
}
@Test
@@ -259,8 +291,8 @@ public class MergedContextConfigurationTests {
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), locations2,
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
- assertFalse(mergedConfig1.equals(mergedConfig2));
- assertFalse(mergedConfig2.equals(mergedConfig1));
+ assertNotEquals(mergedConfig1, mergedConfig2);
+ assertNotEquals(mergedConfig2, mergedConfig1);
}
@Test
@@ -281,8 +313,8 @@ public class MergedContextConfigurationTests {
classes1, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
classes2, EMPTY_STRING_ARRAY, loader);
- assertFalse(mergedConfig1.equals(mergedConfig2));
- assertFalse(mergedConfig2.equals(mergedConfig1));
+ assertNotEquals(mergedConfig1, mergedConfig2);
+ assertNotEquals(mergedConfig2, mergedConfig1);
}
@Test
@@ -325,8 +357,8 @@ public class MergedContextConfigurationTests {
EMPTY_CLASS_ARRAY, activeProfiles1, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, activeProfiles2, loader);
- assertFalse(mergedConfig1.equals(mergedConfig2));
- assertFalse(mergedConfig2.equals(mergedConfig1));
+ assertNotEquals(mergedConfig1, mergedConfig2);
+ assertNotEquals(mergedConfig2, mergedConfig1);
}
@Test
@@ -362,8 +394,42 @@ public class MergedContextConfigurationTests {
EMPTY_CLASS_ARRAY, initializerClasses1, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, initializerClasses2, EMPTY_STRING_ARRAY, loader);
- assertFalse(mergedConfig1.equals(mergedConfig2));
- assertFalse(mergedConfig2.equals(mergedConfig1));
+ assertNotEquals(mergedConfig1, mergedConfig2);
+ assertNotEquals(mergedConfig2, mergedConfig1);
+ }
+
+ /**
+ * @since 3.2.2
+ */
+ @Test
+ public void equalsWithSameParent() {
+ MergedContextConfiguration parent = new MergedContextConfiguration(getClass(), new String[] { "foo", "bar}" },
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, loader, null, parent);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, loader, null, parent);
+ assertEquals(mergedConfig1, mergedConfig2);
+ assertEquals(mergedConfig2, mergedConfig1);
+ }
+
+ /**
+ * @since 3.2.2
+ */
+ @Test
+ public void equalsWithDifferentParents() {
+ MergedContextConfiguration parent1 = new MergedContextConfiguration(getClass(), new String[] { "foo", "bar}" },
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+ MergedContextConfiguration parent2 = new MergedContextConfiguration(getClass(), new String[] { "baz", "quux" },
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, loader, null, parent1);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, loader, null, parent2);
+ assertNotEquals(mergedConfig1, mergedConfig2);
+ assertNotEquals(mergedConfig2, mergedConfig1);
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/SpringRunnerContextCacheTests.java b/spring-test/src/test/java/org/springframework/test/context/SpringRunnerContextCacheTests.java
index d27e3ca50d5..07f7383189c 100644
--- a/spring-test/src/test/java/org/springframework/test/context/SpringRunnerContextCacheTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/SpringRunnerContextCacheTests.java
@@ -44,7 +44,7 @@ import org.springframework.test.context.support.DirtiesContextTestExecutionListe
* @author Sam Brannen
* @author Juergen Hoeller
* @since 2.5
- * @see TestContextCacheKeyTests
+ * @see ContextCacheTests
*/
@RunWith(OrderedMethodsSpringJUnit4ClassRunner.class)
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class })
diff --git a/spring-test/src/test/java/org/springframework/test/context/TestContextCacheKeyTests.java b/spring-test/src/test/java/org/springframework/test/context/TestContextCacheKeyTests.java
deleted file mode 100644
index d7659379409..00000000000
--- a/spring-test/src/test/java/org/springframework/test/context/TestContextCacheKeyTests.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright 2002-2012 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 static org.junit.Assert.assertNotNull;
-import static org.springframework.test.context.SpringRunnerContextCacheTests.assertContextCacheStatistics;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.test.context.support.AnnotationConfigContextLoader;
-
-/**
- * Unit tests for verifying proper behavior of the {@link ContextCache} in
- * conjunction with cache keys used in {@link TestContext}.
- *
- * @author Sam Brannen
- * @since 3.1
- * @see SpringRunnerContextCacheTests
- */
-public class TestContextCacheKeyTests {
-
- private ContextCache contextCache = new ContextCache();
-
-
- @Before
- public void initialCacheState() {
- assertContextCacheStatistics(contextCache, "initial state", 0, 0, 0);
- }
-
- private void loadAppCtxAndAssertCacheStats(Class> testClass, int expectedSize, int expectedHitCount,
- int expectedMissCount) {
- TestContext testContext = new TestContext(testClass, contextCache);
- ApplicationContext context = testContext.getApplicationContext();
- assertNotNull(context);
- assertContextCacheStatistics(contextCache, testClass.getName(), expectedSize, expectedHitCount,
- expectedMissCount);
- }
-
- @Test
- public void verifyCacheKeyIsBasedOnContextLoader() {
- loadAppCtxAndAssertCacheStats(AnnotationConfigContextLoaderTestCase.class, 1, 0, 1);
- loadAppCtxAndAssertCacheStats(AnnotationConfigContextLoaderTestCase.class, 1, 1, 1);
- loadAppCtxAndAssertCacheStats(CustomAnnotationConfigContextLoaderTestCase.class, 2, 1, 2);
- loadAppCtxAndAssertCacheStats(CustomAnnotationConfigContextLoaderTestCase.class, 2, 2, 2);
- loadAppCtxAndAssertCacheStats(AnnotationConfigContextLoaderTestCase.class, 2, 3, 2);
- loadAppCtxAndAssertCacheStats(CustomAnnotationConfigContextLoaderTestCase.class, 2, 4, 2);
- }
-
- @Test
- public void verifyCacheKeyIsBasedOnActiveProfiles() {
- loadAppCtxAndAssertCacheStats(FooBarProfilesTestCase.class, 1, 0, 1);
- loadAppCtxAndAssertCacheStats(FooBarProfilesTestCase.class, 1, 1, 1);
- // Profiles {foo, bar} should hash to the same as {bar,foo}
- loadAppCtxAndAssertCacheStats(BarFooProfilesTestCase.class, 1, 2, 1);
- loadAppCtxAndAssertCacheStats(FooBarProfilesTestCase.class, 1, 3, 1);
- loadAppCtxAndAssertCacheStats(FooBarProfilesTestCase.class, 1, 4, 1);
- loadAppCtxAndAssertCacheStats(BarFooProfilesTestCase.class, 1, 5, 1);
- }
-
-
- @Configuration
- static class Config {
- }
-
- @ContextConfiguration(classes = Config.class, loader = AnnotationConfigContextLoader.class)
- private static class AnnotationConfigContextLoaderTestCase {
- }
-
- @ContextConfiguration(classes = Config.class, loader = CustomAnnotationConfigContextLoader.class)
- private static class CustomAnnotationConfigContextLoaderTestCase {
- }
-
- private static class CustomAnnotationConfigContextLoader extends AnnotationConfigContextLoader {
- }
-
- @ActiveProfiles({ "foo", "bar" })
- @ContextConfiguration(classes = Config.class, loader = AnnotationConfigContextLoader.class)
- private static class FooBarProfilesTestCase {
- }
-
- @ActiveProfiles({ "bar", "foo" })
- @ContextConfiguration(classes = Config.class, loader = AnnotationConfigContextLoader.class)
- private static class BarFooProfilesTestCase {
- }
-
-}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/ClassHierarchyWithMergedConfigLevelOneTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/ClassHierarchyWithMergedConfigLevelOneTests.java
new file mode 100644
index 00000000000..716228c7c35
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/ClassHierarchyWithMergedConfigLevelOneTests.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2002-2013 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.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextHierarchy({
+//
+ @ContextConfiguration(name = "parent", classes = ClassHierarchyWithMergedConfigLevelOneTests.AppConfig.class),//
+ @ContextConfiguration(name = "child", classes = ClassHierarchyWithMergedConfigLevelOneTests.UserConfig.class) //
+})
+public class ClassHierarchyWithMergedConfigLevelOneTests {
+
+ @Configuration
+ static class AppConfig {
+
+ @Bean
+ public String parent() {
+ return "parent";
+ }
+ }
+
+ @Configuration
+ static class UserConfig {
+
+ @Autowired
+ private AppConfig appConfig;
+
+
+ @Bean
+ public String user() {
+ return appConfig.parent() + " + user";
+ }
+
+ @Bean
+ public String beanFromUserConfig() {
+ return "from UserConfig";
+ }
+ }
+
+
+ @Autowired
+ protected String parent;
+
+ @Autowired
+ protected String user;
+
+ @Autowired(required = false)
+ @Qualifier("beanFromUserConfig")
+ protected String beanFromUserConfig;
+
+ @Autowired
+ protected ApplicationContext context;
+
+
+ @Test
+ public void loadContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNotNull("parent ApplicationContext", context.getParent());
+ assertNull("grandparent ApplicationContext", context.getParent().getParent());
+ assertEquals("parent", parent);
+ assertEquals("parent + user", user);
+ assertEquals("from UserConfig", beanFromUserConfig);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/ClassHierarchyWithMergedConfigLevelTwoTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/ClassHierarchyWithMergedConfigLevelTwoTests.java
new file mode 100644
index 00000000000..42a63413d2b
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/ClassHierarchyWithMergedConfigLevelTwoTests.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2002-2013 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.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextHierarchy(@ContextConfiguration(name = "child", classes = ClassHierarchyWithMergedConfigLevelTwoTests.OrderConfig.class))
+public class ClassHierarchyWithMergedConfigLevelTwoTests extends ClassHierarchyWithMergedConfigLevelOneTests {
+
+ @Configuration
+ static class OrderConfig {
+
+ @Autowired
+ private ClassHierarchyWithMergedConfigLevelOneTests.UserConfig userConfig;
+
+ @Bean
+ public String order() {
+ return userConfig.user() + " + order";
+ }
+ }
+
+
+ @Autowired
+ private String order;
+
+
+ @Test
+ @Override
+ public void loadContextHierarchy() {
+ super.loadContextHierarchy();
+ assertEquals("parent + user + order", order);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/ClassHierarchyWithOverriddenConfigLevelTwoTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/ClassHierarchyWithOverriddenConfigLevelTwoTests.java
new file mode 100644
index 00000000000..1f5f69a0290
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/ClassHierarchyWithOverriddenConfigLevelTwoTests.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2002-2013 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.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextHierarchy(@ContextConfiguration(name = "child", classes = ClassHierarchyWithOverriddenConfigLevelTwoTests.TestUserConfig.class, inheritLocations = false))
+public class ClassHierarchyWithOverriddenConfigLevelTwoTests extends ClassHierarchyWithMergedConfigLevelOneTests {
+
+ @Configuration
+ static class TestUserConfig {
+
+ @Autowired
+ private ClassHierarchyWithMergedConfigLevelOneTests.AppConfig appConfig;
+
+
+ @Bean
+ public String user() {
+ return appConfig.parent() + " + test user";
+ }
+
+ @Bean
+ public String beanFromTestUserConfig() {
+ return "from TestUserConfig";
+ }
+ }
+
+
+ @Autowired
+ private String beanFromTestUserConfig;
+
+
+ @Test
+ @Override
+ public void loadContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNotNull("parent ApplicationContext", context.getParent());
+ assertNull("grandparent ApplicationContext", context.getParent().getParent());
+ assertEquals("parent", parent);
+ assertEquals("parent + test user", user);
+ assertEquals("from TestUserConfig", beanFromTestUserConfig);
+ assertNull("Bean from UserConfig should not be present.", beanFromUserConfig);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/DirtiesContextWithContextHierarchyTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/DirtiesContextWithContextHierarchyTests.java
new file mode 100644
index 00000000000..95757b2cd79
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/DirtiesContextWithContextHierarchyTests.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2002-2013 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.hierarchies.standard;
+
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import static org.junit.Assert.*;
+
+/**
+ * Integration tests that verify support for {@link DirtiesContext.HierarchyMode}
+ * in conjunction with context hierarchies configured via {@link ContextHierarchy}.
+ *
+ * Note that correct method execution order is essential, thus the use of
+ * {@link FixMethodOrder}.
+ *
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextHierarchy({ @ContextConfiguration(classes = DirtiesContextWithContextHierarchyTests.ParentConfig.class),
+ @ContextConfiguration(classes = DirtiesContextWithContextHierarchyTests.ChildConfig.class) })
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class DirtiesContextWithContextHierarchyTests {
+
+ @Configuration
+ static class ParentConfig {
+
+ @Bean
+ public StringBuffer foo() {
+ return new StringBuffer("foo");
+ }
+
+ @Bean
+ public StringBuffer baz() {
+ return new StringBuffer("baz-parent");
+ }
+ }
+
+ @Configuration
+ static class ChildConfig {
+
+ @Bean
+ public StringBuffer baz() {
+ return new StringBuffer("baz-child");
+ }
+ }
+
+
+ @Autowired
+ private StringBuffer foo;
+
+ @Autowired
+ private StringBuffer baz;
+
+ @Autowired
+ private ApplicationContext context;
+
+
+ // -------------------------------------------------------------------------
+
+ private void reverseStringBuffers() {
+ foo.reverse();
+ baz.reverse();
+ }
+
+ private void assertOriginalState() {
+ assertCleanParentContext();
+ assertCleanChildContext();
+ }
+
+ private void assertCleanParentContext() {
+ assertEquals("foo", foo.toString());
+ }
+
+ private void assertCleanChildContext() {
+ assertEquals("baz-child", baz.toString());
+ }
+
+ private void assertDirtyParentContext() {
+ assertEquals("oof", foo.toString());
+ }
+
+ private void assertDirtyChildContext() {
+ assertEquals("dlihc-zab", baz.toString());
+ }
+
+ // -------------------------------------------------------------------------
+
+ @Before
+ public void verifyContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNotNull("parent ApplicationContext", context.getParent());
+ assertNull("grandparent ApplicationContext", context.getParent().getParent());
+ }
+
+ @Test
+ public void test1_verifyOriginalStateAndDirtyContexts() {
+ assertOriginalState();
+ reverseStringBuffers();
+ }
+
+ @Test
+ @DirtiesContext
+ public void test2_verifyContextsWereDirtiedAndTriggerExhaustiveCacheClearing() {
+ assertDirtyParentContext();
+ assertDirtyChildContext();
+ }
+
+ @Test
+ @DirtiesContext(hierarchyMode = HierarchyMode.CURRENT_LEVEL)
+ public void test3_verifyOriginalStateWasReinstatedAndDirtyContextsAndTriggerCurrentLevelCacheClearing() {
+ assertOriginalState();
+ reverseStringBuffers();
+ }
+
+ @Test
+ public void test4_verifyParentContextIsStillDirtyButChildContextHasBeenReinstated() {
+ assertDirtyParentContext();
+ assertCleanChildContext();
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithSingleLevelContextHierarchyTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithSingleLevelContextHierarchyTests.java
new file mode 100644
index 00000000000..b111f152705
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithSingleLevelContextHierarchyTests.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2002-2013 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.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextHierarchy(@ContextConfiguration)
+public class SingleTestClassWithSingleLevelContextHierarchyTests {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public String foo() {
+ return "foo";
+ }
+ }
+
+
+ @Autowired
+ private String foo;
+
+ @Autowired
+ private ApplicationContext context;
+
+
+ @Test
+ public void loadContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNull("parent ApplicationContext", context.getParent());
+ assertEquals("foo", foo);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyAndMixedConfigTypesTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyAndMixedConfigTypesTests.java
new file mode 100644
index 00000000000..5591b915b43
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyAndMixedConfigTypesTests.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2002-2013 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.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextHierarchy({
+ @ContextConfiguration(classes = SingleTestClassWithTwoLevelContextHierarchyAndMixedConfigTypesTests.ParentConfig.class),
+ @ContextConfiguration("SingleTestClassWithTwoLevelContextHierarchyAndMixedConfigTypesTests-ChildConfig.xml") })
+public class SingleTestClassWithTwoLevelContextHierarchyAndMixedConfigTypesTests {
+
+ @Configuration
+ static class ParentConfig {
+
+ @Bean
+ public String foo() {
+ return "foo";
+ }
+
+ @Bean
+ public String baz() {
+ return "baz-parent";
+ }
+ }
+
+
+ @Autowired
+ private String foo;
+
+ @Autowired
+ private String bar;
+
+ @Autowired
+ private String baz;
+
+ @Autowired
+ private ApplicationContext context;
+
+
+ @Test
+ public void loadContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNotNull("parent ApplicationContext", context.getParent());
+ assertNull("grandparent ApplicationContext", context.getParent().getParent());
+ assertEquals("foo", foo);
+ assertEquals("bar", bar);
+ assertEquals("baz-child", baz);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyTests.java
new file mode 100644
index 00000000000..0333e5fb2d3
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyTests.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2002-2013 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.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextHierarchy({
+ @ContextConfiguration(classes = SingleTestClassWithTwoLevelContextHierarchyTests.ParentConfig.class),
+ @ContextConfiguration(classes = SingleTestClassWithTwoLevelContextHierarchyTests.ChildConfig.class) })
+public class SingleTestClassWithTwoLevelContextHierarchyTests {
+
+ @Configuration
+ static class ParentConfig {
+
+ @Bean
+ public String foo() {
+ return "foo";
+ }
+
+ @Bean
+ public String baz() {
+ return "baz-parent";
+ }
+ }
+
+ @Configuration
+ static class ChildConfig {
+
+ @Bean
+ public String bar() {
+ return "bar";
+ }
+
+ @Bean
+ public String baz() {
+ return "baz-child";
+ }
+ }
+
+
+ @Autowired
+ private String foo;
+
+ @Autowired
+ private String bar;
+
+ @Autowired
+ private String baz;
+
+ @Autowired
+ private ApplicationContext context;
+
+
+ @Test
+ public void loadContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNotNull("parent ApplicationContext", context.getParent());
+ assertNull("grandparent ApplicationContext", context.getParent().getParent());
+ assertEquals("foo", foo);
+ assertEquals("bar", bar);
+ assertEquals("baz-child", baz);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelOneWithBareContextConfigurationInSubclassTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelOneWithBareContextConfigurationInSubclassTests.java
new file mode 100644
index 00000000000..3dbf5794759
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelOneWithBareContextConfigurationInSubclassTests.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2002-2013 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.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextHierarchy(@ContextConfiguration)
+public class TestHierarchyLevelOneWithBareContextConfigurationInSubclassTests {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public String foo() {
+ return "foo-level-1";
+ }
+
+ @Bean
+ public String bar() {
+ return "bar";
+ }
+ }
+
+
+ @Autowired
+ private String foo;
+
+ @Autowired
+ private String bar;
+
+ @Autowired
+ private ApplicationContext context;
+
+
+ @Test
+ public void loadContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNull("parent ApplicationContext", context.getParent());
+ assertEquals("foo-level-1", foo);
+ assertEquals("bar", bar);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelOneWithBareContextConfigurationInSuperclassTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelOneWithBareContextConfigurationInSuperclassTests.java
new file mode 100644
index 00000000000..16b04c7f180
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelOneWithBareContextConfigurationInSuperclassTests.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2002-2013 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.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+public class TestHierarchyLevelOneWithBareContextConfigurationInSuperclassTests {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public String foo() {
+ return "foo-level-1";
+ }
+
+ @Bean
+ public String bar() {
+ return "bar";
+ }
+ }
+
+
+ @Autowired
+ private String foo;
+
+ @Autowired
+ private String bar;
+
+ @Autowired
+ private ApplicationContext context;
+
+
+ @Test
+ public void loadContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNull("parent ApplicationContext", context.getParent());
+ assertEquals("foo-level-1", foo);
+ assertEquals("bar", bar);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelOneWithSingleLevelContextHierarchyTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelOneWithSingleLevelContextHierarchyTests.java
new file mode 100644
index 00000000000..3e91f375d9b
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelOneWithSingleLevelContextHierarchyTests.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2002-2013 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.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextHierarchy(@ContextConfiguration)
+public class TestHierarchyLevelOneWithSingleLevelContextHierarchyTests {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public String foo() {
+ return "foo-level-1";
+ }
+
+ @Bean
+ public String bar() {
+ return "bar";
+ }
+ }
+
+
+ @Autowired
+ private String foo;
+
+ @Autowired
+ private String bar;
+
+ @Autowired
+ private ApplicationContext context;
+
+
+ @Test
+ public void loadContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNull("parent ApplicationContext", context.getParent());
+ assertEquals("foo-level-1", foo);
+ assertEquals("bar", bar);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithBareContextConfigurationInSubclassTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithBareContextConfigurationInSubclassTests.java
new file mode 100644
index 00000000000..2054f450b93
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithBareContextConfigurationInSubclassTests.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2013 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.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+public class TestHierarchyLevelTwoWithBareContextConfigurationInSubclassTests extends
+ TestHierarchyLevelOneWithBareContextConfigurationInSubclassTests {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public String foo() {
+ return "foo-level-2";
+ }
+
+ @Bean
+ public String baz() {
+ return "baz";
+ }
+ }
+
+
+ @Autowired
+ private String foo;
+
+ @Autowired
+ private String bar;
+
+ @Autowired
+ private String baz;
+
+ @Autowired
+ private ApplicationContext context;
+
+
+ @Test
+ @Override
+ public void loadContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNotNull("parent ApplicationContext", context.getParent());
+ assertNull("grandparent ApplicationContext", context.getParent().getParent());
+ assertEquals("foo-level-2", foo);
+ assertEquals("bar", bar);
+ assertEquals("baz", baz);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithBareContextConfigurationInSuperclassTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithBareContextConfigurationInSuperclassTests.java
new file mode 100644
index 00000000000..bc38d2bb400
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithBareContextConfigurationInSuperclassTests.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2002-2013 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.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextHierarchy(@ContextConfiguration)
+public class TestHierarchyLevelTwoWithBareContextConfigurationInSuperclassTests extends
+ TestHierarchyLevelOneWithBareContextConfigurationInSuperclassTests {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public String foo() {
+ return "foo-level-2";
+ }
+
+ @Bean
+ public String baz() {
+ return "baz";
+ }
+ }
+
+
+ @Autowired
+ private String foo;
+
+ @Autowired
+ private String bar;
+
+ @Autowired
+ private String baz;
+
+ @Autowired
+ private ApplicationContext context;
+
+
+ @Test
+ @Override
+ public void loadContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNotNull("parent ApplicationContext", context.getParent());
+ assertNull("grandparent ApplicationContext", context.getParent().getParent());
+ assertEquals("foo-level-2", foo);
+ assertEquals("bar", bar);
+ assertEquals("baz", baz);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithSingleLevelContextHierarchyAndMixedConfigTypesTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithSingleLevelContextHierarchyAndMixedConfigTypesTests.java
new file mode 100644
index 00000000000..c6edd3af8b6
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithSingleLevelContextHierarchyAndMixedConfigTypesTests.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2002-2013 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.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextHierarchy(@ContextConfiguration)
+public class TestHierarchyLevelTwoWithSingleLevelContextHierarchyAndMixedConfigTypesTests extends
+ TestHierarchyLevelOneWithSingleLevelContextHierarchyTests {
+
+ @Autowired
+ private String foo;
+
+ @Autowired
+ private String bar;
+
+ @Autowired
+ private String baz;
+
+ @Autowired
+ private ApplicationContext context;
+
+
+ @Test
+ @Override
+ public void loadContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNotNull("parent ApplicationContext", context.getParent());
+ assertNull("grandparent ApplicationContext", context.getParent().getParent());
+ assertEquals("foo-level-2", foo);
+ assertEquals("bar", bar);
+ assertEquals("baz", baz);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithSingleLevelContextHierarchyTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithSingleLevelContextHierarchyTests.java
new file mode 100644
index 00000000000..edc8fb33960
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithSingleLevelContextHierarchyTests.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2013 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.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextHierarchy(@ContextConfiguration)
+public class TestHierarchyLevelTwoWithSingleLevelContextHierarchyTests extends
+ TestHierarchyLevelOneWithSingleLevelContextHierarchyTests {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public String foo() {
+ return "foo-level-2";
+ }
+
+ @Bean
+ public String baz() {
+ return "baz";
+ }
+ }
+
+
+ @Autowired
+ private String foo;
+
+ @Autowired
+ private String bar;
+
+ @Autowired
+ private String baz;
+
+ @Autowired
+ private ApplicationContext context;
+
+
+ @Test
+ @Override
+ public void loadContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNotNull("parent ApplicationContext", context.getParent());
+ assertEquals("foo-level-2", foo);
+ assertEquals("bar", bar);
+ assertEquals("baz", baz);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/ControllerIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/ControllerIntegrationTests.java
new file mode 100644
index 00000000000..a6282218f2b
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/ControllerIntegrationTests.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2002-2013 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.hierarchies.web;
+
+import javax.servlet.ServletContext;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.hierarchies.web.ControllerIntegrationTests.AppConfig;
+import org.springframework.test.context.hierarchies.web.ControllerIntegrationTests.WebConfig;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.web.context.WebApplicationContext;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@WebAppConfiguration
+@ContextHierarchy({
+ //
+ @ContextConfiguration(name = "root", classes = AppConfig.class),
+ @ContextConfiguration(name = "dispatcher", classes = WebConfig.class) //
+})
+public class ControllerIntegrationTests {
+
+ @Configuration
+ static class AppConfig {
+
+ @Bean
+ public String foo() {
+ return "foo";
+ }
+ }
+
+ @Configuration
+ static class WebConfig {
+
+ @Bean
+ public String bar() {
+ return "bar";
+ }
+ }
+
+
+ // -------------------------------------------------------------------------
+
+ @Autowired
+ private WebApplicationContext wac;
+
+ @Autowired
+ private String foo;
+
+ @Autowired
+ private String bar;
+
+
+ @Test
+ public void verifyRootWacSupport() {
+ assertEquals("foo", foo);
+ assertEquals("bar", bar);
+
+ ApplicationContext parent = wac.getParent();
+ assertNotNull(parent);
+ assertTrue(parent instanceof WebApplicationContext);
+ WebApplicationContext root = (WebApplicationContext) parent;
+ assertFalse(root.getBeansOfType(String.class).containsKey("bar"));
+
+ ServletContext childServletContext = wac.getServletContext();
+ assertNotNull(childServletContext);
+ ServletContext rootServletContext = root.getServletContext();
+ assertNotNull(rootServletContext);
+ assertSame(childServletContext, rootServletContext);
+
+ assertSame(root, rootServletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE));
+ assertSame(root, childServletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE));
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/DispatcherWacRootWacEarTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/DispatcherWacRootWacEarTests.java
new file mode 100644
index 00000000000..622547c48b8
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/DispatcherWacRootWacEarTests.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2002-2013 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.hierarchies.web;
+
+import javax.servlet.ServletContext;
+
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.web.context.WebApplicationContext;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@ContextHierarchy(@ContextConfiguration)
+public class DispatcherWacRootWacEarTests extends RootWacEarTests {
+
+ @Autowired
+ private WebApplicationContext wac;
+
+ @Autowired
+ private String ear;
+
+ @Autowired
+ private String root;
+
+ @Autowired
+ private String dispatcher;
+
+
+ @Test
+ @Override
+ public void verifyEarConfig() {
+ /* no-op */
+ }
+
+ @Test
+ @Override
+ public void verifyRootWacConfig() {
+ /* no-op */
+ }
+
+ @Test
+ public void verifyDispatcherWacConfig() {
+ ApplicationContext parent = wac.getParent();
+ assertNotNull(parent);
+ assertTrue(parent instanceof WebApplicationContext);
+
+ ApplicationContext grandParent = parent.getParent();
+ assertNotNull(grandParent);
+ assertFalse(grandParent instanceof WebApplicationContext);
+
+ ServletContext dispatcherServletContext = wac.getServletContext();
+ assertNotNull(dispatcherServletContext);
+ ServletContext rootServletContext = ((WebApplicationContext) parent).getServletContext();
+ assertNotNull(rootServletContext);
+ assertSame(dispatcherServletContext, rootServletContext);
+
+ assertSame(parent,
+ rootServletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE));
+ assertSame(parent,
+ dispatcherServletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE));
+
+ assertEquals("ear", ear);
+ assertEquals("root", root);
+ assertEquals("dispatcher", dispatcher);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/EarTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/EarTests.java
new file mode 100644
index 00000000000..8cd7eaf1b4e
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/EarTests.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2002-2013 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.hierarchies.web;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.web.context.WebApplicationContext;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+public class EarTests {
+
+ @Configuration
+ static class EarConfig {
+
+ @Bean
+ public String ear() {
+ return "ear";
+ }
+ }
+
+
+ // -------------------------------------------------------------------------
+
+ @Autowired
+ private ApplicationContext context;
+
+ @Autowired
+ private String ear;
+
+
+ @Test
+ public void verifyEarConfig() {
+ assertFalse(context instanceof WebApplicationContext);
+ assertNull(context.getParent());
+ assertEquals("ear", ear);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/RootWacEarTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/RootWacEarTests.java
new file mode 100644
index 00000000000..dbd3e93aa85
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/RootWacEarTests.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2002-2013 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.hierarchies.web;
+
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.web.context.WebApplicationContext;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@WebAppConfiguration
+@ContextHierarchy(@ContextConfiguration)
+public class RootWacEarTests extends EarTests {
+
+ @Configuration
+ static class RootWacConfig {
+
+ @Bean
+ public String root() {
+ return "root";
+ }
+ }
+
+
+ // -------------------------------------------------------------------------
+
+ @Autowired
+ private WebApplicationContext wac;
+
+ @Autowired
+ private String ear;
+
+ @Autowired
+ private String root;
+
+
+ @Test
+ @Override
+ public void verifyEarConfig() {
+ /* no-op */
+ }
+
+ @Test
+ public void verifyRootWacConfig() {
+ ApplicationContext parent = wac.getParent();
+ assertNotNull(parent);
+ assertFalse(parent instanceof WebApplicationContext);
+ assertEquals("ear", ear);
+ assertEquals("root", root);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests.java
index b5d74839f68..fd82c559b67 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests.java
@@ -33,21 +33,21 @@ import org.springframework.test.context.support.GenericPropertiesContextLoader;
/**
*
* JUnit 4 based test class, which verifies the expected functionality of
- * {@link SpringJUnit4ClassRunner} in conjunction with support for application
- * contexts loaded from Java {@link Properties} files. Specifically, the
- * {@link ContextConfiguration#loader() loaderClass} and
- * {@link ContextConfiguration#resourceSuffix() resourceSuffix} attributes of
- * @ContextConfiguration are tested.
+ * {@link SpringJUnit4ClassRunner} in conjunction with support for application contexts
+ * loaded from Java {@link Properties} files. Specifically, the
+ * {@link ContextConfiguration#loader() loader} attribute of {@code ContextConfiguration}
+ * and the
+ * {@link org.springframework.test.context.support.GenericPropertiesContextLoader#getResourceSuffix()
+ * resourceSuffix} property of {@code GenericPropertiesContextLoader} are tested.
*
*
- * Since no {@link ContextConfiguration#locations() locations} are explicitly
- * defined, the {@link ContextConfiguration#resourceSuffix() resourceSuffix} is
- * set to "-context.properties", and
- * {@link ContextConfiguration#generateDefaultLocations() generateDefaultLocations}
- * is left set to its default value of {@code true}, this test class's
- * dependencies will be injected via
- * {@link Autowired annotation-based autowiring} from beans defined in the
- * {@link ApplicationContext} loaded from the default classpath resource: "{@code /org/springframework/test/junit4/PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests-context.properties}".
+ * Since no {@link ContextConfiguration#locations() locations} are explicitly defined, the
+ * {@code resourceSuffix} is set to "-context.properties", and since default
+ * resource locations will be detected by default, this test class's dependencies will be
+ * injected via {@link Autowired annotation-based autowiring} from beans defined in the
+ * {@link ApplicationContext} loaded from the default classpath resource: "
+ * {@code /org/springframework/test/junit4/PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests-context.properties}
+ * ".
*
*
* @author Sam Brannen
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/Spr8849Tests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/Spr8849Tests.java
index fa0412abc1b..dd589867177 100644
--- a/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/Spr8849Tests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/Spr8849Tests.java
@@ -38,6 +38,7 @@ import org.junit.runners.Suite.SuiteClasses;
* @author Sam Brannen
* @since 3.2
*/
+@SuppressWarnings("javadoc")
@RunWith(Suite.class)
@SuiteClasses({ TestClass1.class, TestClass2.class })
public class Spr8849Tests {
diff --git a/spring-test/src/test/java/org/springframework/test/context/testng/AnnotationConfigTransactionalTestNGSpringContextTests.java b/spring-test/src/test/java/org/springframework/test/context/testng/AnnotationConfigTransactionalTestNGSpringContextTests.java
index f20e466410f..66ffc2026c5 100644
--- a/spring-test/src/test/java/org/springframework/test/context/testng/AnnotationConfigTransactionalTestNGSpringContextTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/testng/AnnotationConfigTransactionalTestNGSpringContextTests.java
@@ -43,10 +43,11 @@ import org.testng.annotations.Test;
/**
* Integration tests that verify support for
- * {@link import org.springframework.context.annotation.Configuration @Configuration}
- * classes with TestNG-based tests.
+ * {@link org.springframework.context.annotation.Configuration @Configuration} classes
+ * with TestNG-based tests.
*
- * Configuration will be loaded from
+ *
+ * Configuration will be loaded from
* {@link AnnotationConfigTransactionalTestNGSpringContextTests.ContextConfiguration}.
*
* @author Sam Brannen
diff --git a/spring-test/src/test/resources/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyAndMixedConfigTypesTests-ChildConfig.xml b/spring-test/src/test/resources/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyAndMixedConfigTypesTests-ChildConfig.xml
new file mode 100644
index 00000000000..2189b42feee
--- /dev/null
+++ b/spring-test/src/test/resources/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyAndMixedConfigTypesTests-ChildConfig.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
diff --git a/spring-test/src/test/resources/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithSingleLevelContextHierarchyAndMixedConfigTypesTests-context.xml b/spring-test/src/test/resources/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithSingleLevelContextHierarchyAndMixedConfigTypesTests-context.xml
new file mode 100644
index 00000000000..3abc44bda6a
--- /dev/null
+++ b/spring-test/src/test/resources/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithSingleLevelContextHierarchyAndMixedConfigTypesTests-context.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
diff --git a/spring-test/src/test/resources/org/springframework/test/context/hierarchies/web/DispatcherWacRootWacEarTests-context.xml b/spring-test/src/test/resources/org/springframework/test/context/hierarchies/web/DispatcherWacRootWacEarTests-context.xml
new file mode 100644
index 00000000000..496fc0b08e4
--- /dev/null
+++ b/spring-test/src/test/resources/org/springframework/test/context/hierarchies/web/DispatcherWacRootWacEarTests-context.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/src/dist/changelog.txt b/src/dist/changelog.txt
index 1e6da7b4efe..3d29e95e6ec 100644
--- a/src/dist/changelog.txt
+++ b/src/dist/changelog.txt
@@ -41,6 +41,8 @@ Changes in version 3.2.2 (2013-03-11)
* MappingJackson(2)JsonView allows subclasses to access the ObjectMapper and to override content writing (SPR-7619)
* Log4jWebConfigurer supports resolving placeholders against ServletContext init-parameters as well (SPR-10284)
* consistent use of LinkedHashMaps and independent getAttributeNames Enumeration in Servlet/Portlet mocks (SPR-10224)
+* introduced support for context hierarchies in the TestContext framework (SPR-5613)
+* introduced support for WebApplicationContext hierarchies in the TestContext framework (SPR-9863)
Changes in version 3.2.1 (2013-01-24)