[SPR-7960][SPR-8386] Supporting declarative configuration of bean definition profiles in the TestContext framework:
- TextContext now works with MergedContextConfiguration instead of locations and loader - TextContext now builds context caching key from MergedContextConfiguration - Test context caching is now based on locations, classes, active profiles, and context loader - TextContext now delegates to SmartContextLoader or ContextLoader as appropriate - AbstractContextLoader now implements SmartContextLoader - AbstractGenericContextLoader now sets active profiles in the GenericApplicationContext - Introduced integration tests for profile support in the TCF for both XML and annotation config
This commit is contained in:
@@ -46,8 +46,7 @@ class ContextCache {
|
||||
/**
|
||||
* Map of context keys to Spring ApplicationContext instances.
|
||||
*/
|
||||
private final Map<String, ApplicationContext> contextKeyToContextMap =
|
||||
new ConcurrentHashMap<String, ApplicationContext>();
|
||||
private final Map<String, ApplicationContext> contextKeyToContextMap = new ConcurrentHashMap<String, ApplicationContext>();
|
||||
|
||||
private int hitCount;
|
||||
|
||||
@@ -134,7 +133,7 @@ class ContextCache {
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly add a ApplicationContext instance to the cache under the given key.
|
||||
* Explicitly add an ApplicationContext instance to the cache under the given key.
|
||||
* @param key the context key (never <code>null</code>)
|
||||
* @param context the ApplicationContext instance (never <code>null</code>)
|
||||
*/
|
||||
@@ -188,11 +187,11 @@ class ContextCache {
|
||||
* as the {@link #hitCount hit} and {@link #missCount miss} counts.
|
||||
*/
|
||||
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())//
|
||||
.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.test.context;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -43,9 +42,9 @@ public class TestContext extends AttributeAccessorSupport {
|
||||
|
||||
private final ContextCache contextCache;
|
||||
|
||||
private final ContextLoader contextLoader;
|
||||
private final String contextKey;
|
||||
|
||||
private final String[] locations;
|
||||
private final MergedContextConfiguration mergedContextConfiguration;
|
||||
|
||||
private final Class<?> testClass;
|
||||
|
||||
@@ -88,28 +87,28 @@ public class TestContext extends AttributeAccessorSupport {
|
||||
Assert.notNull(testClass, "Test class must not be null");
|
||||
Assert.notNull(contextCache, "ContextCache must not be null");
|
||||
|
||||
MergedContextConfiguration mergedContextConfiguration;
|
||||
ContextConfiguration contextConfiguration = testClass.getAnnotation(ContextConfiguration.class);
|
||||
ContextLoader contextLoader = null;
|
||||
String[] locations = null;
|
||||
|
||||
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);
|
||||
}
|
||||
else {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace(String.format("Retrieved @ContextConfiguration [%s] for class [%s]", contextConfiguration,
|
||||
testClass));
|
||||
}
|
||||
contextLoader = ContextLoaderUtils.resolveContextLoader(testClass, defaultContextLoaderClassName);
|
||||
locations = ContextLoaderUtils.resolveContextLocations(contextLoader, testClass);
|
||||
mergedContextConfiguration = ContextLoaderUtils.buildMergedContextConfiguration(testClass,
|
||||
defaultContextLoaderClassName);
|
||||
}
|
||||
|
||||
this.testClass = testClass;
|
||||
this.contextCache = contextCache;
|
||||
this.contextLoader = contextLoader;
|
||||
this.locations = locations;
|
||||
this.contextKey = generateContextKey(mergedContextConfiguration);
|
||||
this.mergedContextConfiguration = mergedContextConfiguration;
|
||||
this.testClass = testClass;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,19 +117,44 @@ public class TestContext extends AttributeAccessorSupport {
|
||||
* @throws Exception if an error occurs while loading the application context
|
||||
*/
|
||||
private ApplicationContext loadApplicationContext() throws Exception {
|
||||
Assert.notNull(this.contextLoader, "Can not load an ApplicationContext with a NULL 'contextLoader'. "
|
||||
ContextLoader contextLoader = mergedContextConfiguration.getContextLoader();
|
||||
Assert.notNull(contextLoader, "Can not load an ApplicationContext with a NULL 'contextLoader'. "
|
||||
+ "Consider annotating your test class with @ContextConfiguration.");
|
||||
Assert.notNull(this.locations, "Can not load an ApplicationContext with a NULL 'locations' array. "
|
||||
+ "Consider annotating your test class with @ContextConfiguration.");
|
||||
return this.contextLoader.loadContext(this.locations);
|
||||
|
||||
ApplicationContext applicationContext;
|
||||
|
||||
if (contextLoader instanceof SmartContextLoader) {
|
||||
SmartContextLoader smartContextLoader = (SmartContextLoader) contextLoader;
|
||||
applicationContext = smartContextLoader.loadContext(mergedContextConfiguration);
|
||||
}
|
||||
else {
|
||||
String[] locations = mergedContextConfiguration.getLocations();
|
||||
Assert.notNull(locations, "Can not load an ApplicationContext with a NULL 'locations' array. "
|
||||
+ "Consider annotating your test class with @ContextConfiguration.");
|
||||
applicationContext = contextLoader.loadContext(locations);
|
||||
}
|
||||
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the supplied context <code>key</code> to a String representation
|
||||
* for use in caching, logging, etc.
|
||||
* Generates a context <code>key</code> from information stored in the
|
||||
* {@link MergedContextConfiguration} for this <code>TestContext</code>.
|
||||
*/
|
||||
private String contextKeyString(Serializable key) {
|
||||
return ObjectUtils.nullSafeToString(key);
|
||||
private String generateContextKey(MergedContextConfiguration mergedContextConfiguration) {
|
||||
|
||||
String[] locations = mergedContextConfiguration.getLocations();
|
||||
Class<?>[] classes = mergedContextConfiguration.getClasses();
|
||||
String[] activeProfiles = mergedContextConfiguration.getActiveProfiles();
|
||||
ContextLoader contextLoader = mergedContextConfiguration.getContextLoader();
|
||||
|
||||
String locationsKey = ObjectUtils.nullSafeToString(locations);
|
||||
String classesKey = ObjectUtils.nullSafeToString(classes);
|
||||
String activeProfilesKey = ObjectUtils.nullSafeToString(activeProfiles);
|
||||
String contextLoaderKey = contextLoader == null ? "null" : contextLoader.getClass().getName();
|
||||
|
||||
return String.format("locations = [%s], classes = [%s], activeProfiles = [%s], contextLoader = [%s]",
|
||||
locationsKey, classesKey, activeProfilesKey, contextLoaderKey);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,13 +165,12 @@ public class TestContext extends AttributeAccessorSupport {
|
||||
* application context
|
||||
*/
|
||||
public ApplicationContext getApplicationContext() {
|
||||
synchronized (this.contextCache) {
|
||||
String contextKeyString = contextKeyString(this.locations);
|
||||
ApplicationContext context = this.contextCache.get(contextKeyString);
|
||||
synchronized (contextCache) {
|
||||
ApplicationContext context = contextCache.get(contextKey);
|
||||
if (context == null) {
|
||||
try {
|
||||
context = loadApplicationContext();
|
||||
this.contextCache.put(contextKeyString, context);
|
||||
contextCache.put(contextKey, context);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Failed to load ApplicationContext", ex);
|
||||
@@ -162,7 +185,7 @@ public class TestContext extends AttributeAccessorSupport {
|
||||
* @return the test class (never <code>null</code>)
|
||||
*/
|
||||
public final Class<?> getTestClass() {
|
||||
return this.testClass;
|
||||
return testClass;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -172,7 +195,7 @@ public class TestContext extends AttributeAccessorSupport {
|
||||
* @see #updateState(Object,Method,Throwable)
|
||||
*/
|
||||
public final Object getTestInstance() {
|
||||
return this.testInstance;
|
||||
return testInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,7 +205,7 @@ public class TestContext extends AttributeAccessorSupport {
|
||||
* @see #updateState(Object, Method, Throwable)
|
||||
*/
|
||||
public final Method getTestMethod() {
|
||||
return this.testMethod;
|
||||
return testMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,7 +217,7 @@ public class TestContext extends AttributeAccessorSupport {
|
||||
* @see #updateState(Object, Method, Throwable)
|
||||
*/
|
||||
public final Throwable getTestException() {
|
||||
return this.testException;
|
||||
return testException;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,7 +227,7 @@ public class TestContext extends AttributeAccessorSupport {
|
||||
* replacing a bean definition).
|
||||
*/
|
||||
public void markApplicationContextDirty() {
|
||||
this.contextCache.setDirty(contextKeyString(this.locations));
|
||||
contextCache.setDirty(contextKey);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -227,11 +250,11 @@ public class TestContext extends AttributeAccessorSupport {
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this)//
|
||||
.append("testClass", this.testClass)//
|
||||
.append("locations", this.locations)//
|
||||
.append("testInstance", this.testInstance)//
|
||||
.append("testMethod", this.testMethod)//
|
||||
.append("testException", this.testException)//
|
||||
.append("testClass", testClass)//
|
||||
.append("testInstance", testInstance)//
|
||||
.append("testMethod", testMethod)//
|
||||
.append("testException", testException)//
|
||||
.append("mergedContextConfiguration", mergedContextConfiguration)//
|
||||
.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.io.support.ResourcePatternUtils;
|
||||
import org.springframework.test.context.ContextLoader;
|
||||
import org.springframework.test.context.ResourceTypeAwareContextLoader;
|
||||
import org.springframework.test.context.SmartContextLoader;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@@ -38,7 +39,7 @@ import org.springframework.util.StringUtils;
|
||||
* @see #generateDefaultLocations
|
||||
* @see #modifyLocations
|
||||
*/
|
||||
public abstract class AbstractContextLoader implements ResourceTypeAwareContextLoader {
|
||||
public abstract class AbstractContextLoader implements SmartContextLoader, ResourceTypeAwareContextLoader {
|
||||
|
||||
/**
|
||||
* If the supplied <code>locations</code> are <code>null</code> or
|
||||
|
||||
@@ -20,9 +20,12 @@ 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;
|
||||
import org.springframework.test.context.MergedContextConfiguration;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -45,6 +48,33 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
|
||||
protected static final Log logger = LogFactory.getLog(AbstractGenericContextLoader.class);
|
||||
|
||||
|
||||
/**
|
||||
* TODO Document loadContext().
|
||||
*
|
||||
* @see org.springframework.test.context.SmartContextLoader#loadContext(org.springframework.test.context.MergedContextConfiguration)
|
||||
*/
|
||||
public final ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration) throws Exception {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("Loading ApplicationContext for merged context configuration [%s].",
|
||||
mergedContextConfiguration));
|
||||
}
|
||||
|
||||
String[] locations = mergedContextConfiguration.getLocations();
|
||||
Assert.notNull(locations, "Can not load an ApplicationContext with a NULL 'locations' array. "
|
||||
+ "Consider annotating your test class with @ContextConfiguration.");
|
||||
|
||||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
context.getEnvironment().setActiveProfiles(mergedContextConfiguration.getActiveProfiles());
|
||||
prepareContext(context);
|
||||
customizeBeanFactory(context.getDefaultListableBeanFactory());
|
||||
loadBeanDefinitions(context, locations);
|
||||
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
|
||||
customizeContext(context);
|
||||
context.refresh();
|
||||
context.registerShutdownHook();
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a Spring ApplicationContext from the supplied <code>locations</code>.
|
||||
* <p>Implementation details:
|
||||
|
||||
Reference in New Issue
Block a user