Browse Source

Support automatic discovery of default TELs

Prior to this commit, there was no declarative mechanism for a custom
TestExecutionListener to be registered as a default
TestExecutionListener.

This commit introduces support for discovering default
TestExecutionListener implementations via the SpringFactoriesLoader
mechanism. Specifically, the spring-test module declares all core
default TestExecutionListeners under the
org.springframework.test.context.TestExecutionListener key in its
META-INF/spring.factories properties file, and third-party frameworks
and developers can contribute to the list of default
TestExecutionListeners in the same manner.

 - AbstractTestContextBootstrapper uses the SpringFactoriesLoader to
   look up the class names of all registered default
   TestExecutionListeners and sorts the instantiated listeners using
   AnnotationAwareOrderComparator.

 - DefaultTestContextBootstrapper and WebTestContextBootstrapper now
   rely on the SpringFactoriesLoader mechanism for finding default
   TestExecutionListeners instead of hard coding fully qualified class
   names.

 - To ensure that default TestExecutionListeners are registered in the
   correct order, each can implement Ordered or declare @Order.

 - AbstractTestExecutionListener and all default TestExecutionListeners
   provided by Spring now implement Ordered with appropriate values.

 - Introduced "copy constructors" in MergedContextConfiguration and
   WebMergedContextConfiguration

 - SpringFactoriesLoader now uses AnnotationAwareOrderComparator
   instead of OrderComparator.

Issue: SPR-11466
pull/623/merge
Sam Brannen 12 years ago
parent
commit
e6d16148e5
  1. 9
      spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java
  2. 13
      spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java
  3. 12
      spring-test/src/main/java/org/springframework/test/context/TestContextBootstrapper.java
  4. 8
      spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java
  5. 8
      spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java
  6. 107
      spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java
  7. 17
      spring-test/src/main/java/org/springframework/test/context/support/AbstractTestExecutionListener.java
  8. 61
      spring-test/src/main/java/org/springframework/test/context/support/DefaultTestContextBootstrapper.java
  9. 13
      spring-test/src/main/java/org/springframework/test/context/support/DependencyInjectionTestExecutionListener.java
  10. 36
      spring-test/src/main/java/org/springframework/test/context/support/DirtiesContextTestExecutionListener.java
  11. 8
      spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java
  12. 8
      spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java
  13. 16
      spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java
  14. 57
      spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java
  15. 8
      spring-test/src/main/resources/META-INF/spring.factories
  16. 2
      spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java
  17. 25
      spring-test/src/test/java/org/springframework/test/context/jdbc/TransactionalSqlScriptsTests.java
  18. 2
      spring-test/src/test/resources/META-INF/spring.factories

9
spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java

@ -27,7 +27,7 @@ import java.util.Properties;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.OrderComparator; import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.io.UrlResource; import org.springframework.core.io.UrlResource;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@ -48,12 +48,13 @@ import org.springframework.util.StringUtils;
* *
* @author Arjen Poutsma * @author Arjen Poutsma
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Sam Brannen
* @since 3.2 * @since 3.2
*/ */
public abstract class SpringFactoriesLoader { public abstract class SpringFactoriesLoader {
/** The location to look for the factories. Can be present in multiple JAR files. */ /** The location to look for the factories. Can be present in multiple JAR files. */
private static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class); private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
@ -61,7 +62,7 @@ public abstract class SpringFactoriesLoader {
/** /**
* Load the factory implementations of the given type from the default location, * Load the factory implementations of the given type from the default location,
* using the given class loader. * using the given class loader.
* <p>The returned factories are ordered in accordance with the {@link OrderComparator}. * <p>The returned factories are ordered in accordance with the {@link AnnotationAwareOrderComparator}.
* @param factoryClass the interface or abstract class representing the factory * @param factoryClass the interface or abstract class representing the factory
* @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default) * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default)
*/ */
@ -79,7 +80,7 @@ public abstract class SpringFactoriesLoader {
for (String factoryName : factoryNames) { for (String factoryName : factoryNames) {
result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse)); result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
} }
OrderComparator.sort(result); AnnotationAwareOrderComparator.sort(result);
return result; return result;
} }

13
spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java

@ -79,6 +79,7 @@ public class MergedContextConfiguration implements Serializable {
private final String[] propertySourceProperties; private final String[] propertySourceProperties;
private final ContextLoader contextLoader; private final ContextLoader contextLoader;
private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate; private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate;
private final MergedContextConfiguration parent; private final MergedContextConfiguration parent;
@ -185,6 +186,18 @@ public class MergedContextConfiguration implements Serializable {
cacheAwareContextLoaderDelegate, parent); cacheAwareContextLoaderDelegate, parent);
} }
/**
* Create a new {@code MergedContextConfiguration} instance by copying
* all fields from the supplied {@code MergedContextConfiguration}.
* @since 4.1
*/
public MergedContextConfiguration(MergedContextConfiguration mergedConfig) {
this(mergedConfig.testClass, mergedConfig.locations, mergedConfig.classes,
mergedConfig.contextInitializerClasses, mergedConfig.activeProfiles, mergedConfig.propertySourceLocations,
mergedConfig.propertySourceProperties, mergedConfig.contextLoader,
mergedConfig.cacheAwareContextLoaderDelegate, mergedConfig.parent);
}
/** /**
* Create a new {@code MergedContextConfiguration} instance for the * Create a new {@code MergedContextConfiguration} instance for the
* supplied parameters. * supplied parameters.

12
spring-test/src/main/java/org/springframework/test/context/TestContextBootstrapper.java

@ -63,8 +63,16 @@ public interface TestContextBootstrapper {
* for the test class in the {@link BootstrapContext} associated with this bootstrapper. * for the test class in the {@link BootstrapContext} associated with this bootstrapper.
* <p>If {@link TestExecutionListeners @TestExecutionListeners} is not * <p>If {@link TestExecutionListeners @TestExecutionListeners} is not
* <em>present</em> on the test class in the {@code BootstrapContext}, * <em>present</em> on the test class in the {@code BootstrapContext},
* <em>default</em> listeners should be returned. Concrete implementations * <em>default</em> listeners should be returned. Furthermore, default
* are free to determine what comprises the set of default listeners. * listeners must be sorted using
* {@link org.springframework.core.annotation.AnnotationAwareOrderComparator
* AnnotationAwareOrderComparator}.
* <p>Concrete implementations are free to determine what comprises the
* set of default listeners. However, by default, the Spring TestContext
* Framework will use the
* {@link org.springframework.core.io.support.SpringFactoriesLoader SpringFactoriesLoader}
* mechanism to look up all {@code TestExecutionListener} class names
* configured in all {@code META-INF/spring.factories} files on the classpath.
* <p>The {@link TestExecutionListeners#inheritListeners() inheritListeners} * <p>The {@link TestExecutionListeners#inheritListeners() inheritListeners}
* flag of {@link TestExecutionListeners @TestExecutionListeners} must be * flag of {@link TestExecutionListeners @TestExecutionListeners} must be
* taken into consideration. Specifically, if the {@code inheritListeners} * taken into consideration. Specifically, if the {@code inheritListeners}

8
spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java

@ -23,7 +23,13 @@ package org.springframework.test.context;
* <p>Concrete implementations must provide a {@code public} no-args constructor, * <p>Concrete implementations must provide a {@code public} no-args constructor,
* so that listeners can be instantiated transparently by tools and configuration * so that listeners can be instantiated transparently by tools and configuration
* mechanisms. * mechanisms.
* <p>Spring provides the following out-of-the-box implementations: * <p>Implementations may optionally declare the position in which they should
* be ordered among the chain of default listeners via the
* {@link org.springframework.core.Ordered Order} interface or
* {@link org.springframework.core.annotation.Order @Order} annotation. See
* {@link TestContextBootstrapper#getTestExecutionListeners()} for details.
* <p>Spring provides the following out-of-the-box implementations (all of
* which are annotated with {@code @Order}):
* <ul> * <ul>
* <li>{@link org.springframework.test.context.web.ServletTestExecutionListener * <li>{@link org.springframework.test.context.web.ServletTestExecutionListener
* ServletTestExecutionListener}</li> * ServletTestExecutionListener}</li>

8
spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java

@ -89,6 +89,14 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
private static final Log logger = LogFactory.getLog(SqlScriptsTestExecutionListener.class); private static final Log logger = LogFactory.getLog(SqlScriptsTestExecutionListener.class);
/**
* Returns {@code 5000}.
*/
@Override
public final int getOrder() {
return 5000;
}
/** /**
* Execute SQL scripts configured via {@link Sql @Sql} for the supplied * Execute SQL scripts configured via {@link Sql @Sql} for the supplied
* {@link TestContext} <em>before</em> the current test method. * {@link TestContext} <em>before</em> the current test method.

107
spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java

@ -31,7 +31,9 @@ import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.test.context.BootstrapContext; import org.springframework.test.context.BootstrapContext;
import org.springframework.test.context.CacheAwareContextLoaderDelegate; import org.springframework.test.context.CacheAwareContextLoaderDelegate;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
@ -55,11 +57,10 @@ import org.springframework.util.StringUtils;
* provides most of the behavior required by a bootstrapper. * provides most of the behavior required by a bootstrapper.
* *
* <p>Concrete subclasses typically will only need to provide implementations for * <p>Concrete subclasses typically will only need to provide implementations for
* the following {@code abstract} methods: * the following methods:
* <ul> * <ul>
* <li>{@link #getDefaultTestExecutionListenerClassNames}
* <li>{@link #getDefaultContextLoaderClass} * <li>{@link #getDefaultContextLoaderClass}
* <li>{@link #buildMergedContextConfiguration} * <li>{@link #processMergedContextConfiguration}
* </ul> * </ul>
* *
* @author Sam Brannen * @author Sam Brannen
@ -98,6 +99,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
Class<?> clazz = getBootstrapContext().getTestClass(); Class<?> clazz = getBootstrapContext().getTestClass();
Class<TestExecutionListeners> annotationType = TestExecutionListeners.class; Class<TestExecutionListeners> annotationType = TestExecutionListeners.class;
List<Class<? extends TestExecutionListener>> classesList = new ArrayList<Class<? extends TestExecutionListener>>(); List<Class<? extends TestExecutionListener>> classesList = new ArrayList<Class<? extends TestExecutionListener>>();
boolean usingDefaults = false;
AnnotationDescriptor<TestExecutionListeners> descriptor = MetaAnnotationUtils.findAnnotationDescriptor(clazz, AnnotationDescriptor<TestExecutionListeners> descriptor = MetaAnnotationUtils.findAnnotationDescriptor(clazz,
annotationType); annotationType);
@ -105,9 +107,10 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
// Use defaults? // Use defaults?
if (descriptor == null) { if (descriptor == null) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("@TestExecutionListeners is not present for class [" + clazz.getName() logger.debug(String.format("@TestExecutionListeners is not present for class [%s]: using defaults.",
+ "]: using defaults."); clazz.getName()));
} }
usingDefaults = true;
classesList.addAll(getDefaultTestExecutionListenerClasses()); classesList.addAll(getDefaultTestExecutionListenerClasses());
} }
else { else {
@ -142,6 +145,20 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
} }
} }
List<TestExecutionListener> listeners = instantiateListeners(classesList);
// Sort by Ordered/@Order if we loaded default listeners.
if (usingDefaults) {
AnnotationAwareOrderComparator.sort(listeners);
}
if (logger.isInfoEnabled()) {
logger.info(String.format("Using TestExecutionListeners: %s", listeners));
}
return listeners;
}
private List<TestExecutionListener> instantiateListeners(List<Class<? extends TestExecutionListener>> classesList) {
List<TestExecutionListener> listeners = new ArrayList<TestExecutionListener>(classesList.size()); List<TestExecutionListener> listeners = new ArrayList<TestExecutionListener>(classesList.size());
for (Class<? extends TestExecutionListener> listenerClass : classesList) { for (Class<? extends TestExecutionListener> listenerClass : classesList) {
NoClassDefFoundError ncdfe = null; NoClassDefFoundError ncdfe = null;
@ -194,6 +211,28 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
return defaultListenerClasses; return defaultListenerClasses;
} }
/**
* Get the names of the default {@link TestExecutionListener} classes for
* this bootstrapper.
* <p>The default implementation looks up all
* {@code org.springframework.test.context.TestExecutionListener} entries
* configured in all {@code META-INF/spring.factories} files on the classpath.
* <p>This method is invoked by {@link #getDefaultTestExecutionListenerClasses()}.
* @return an <em>unmodifiable</em> list of names of default {@code TestExecutionListener}
* classes
* @see SpringFactoriesLoader#loadFactoryNames
*/
protected List<String> getDefaultTestExecutionListenerClassNames() {
final List<String> classNames = SpringFactoriesLoader.loadFactoryNames(TestExecutionListener.class,
getClass().getClassLoader());
if (logger.isInfoEnabled()) {
logger.info(String.format("Loaded default TestExecutionListener class names from location [%s]: %s",
SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION, classNames));
}
return Collections.unmodifiableList(classNames);
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@ -302,9 +341,11 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
String[] activeProfiles = ActiveProfilesUtils.resolveActiveProfiles(testClass); String[] activeProfiles = ActiveProfilesUtils.resolveActiveProfiles(testClass);
MergedTestPropertySources mergedTestPropertySources = TestPropertySourceUtils.buildMergedTestPropertySources(testClass); MergedTestPropertySources mergedTestPropertySources = TestPropertySourceUtils.buildMergedTestPropertySources(testClass);
return buildMergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles, MergedContextConfiguration mergedConfig = new MergedContextConfiguration(testClass, locations, classes,
mergedTestPropertySources.getLocations(), mergedTestPropertySources.getProperties(), contextLoader, initializerClasses, activeProfiles, mergedTestPropertySources.getLocations(),
cacheAwareContextLoaderDelegate, parentConfig); mergedTestPropertySources.getProperties(), contextLoader, cacheAwareContextLoaderDelegate, parentConfig);
return processMergedContextConfiguration(mergedConfig);
} }
/** /**
@ -383,15 +424,6 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
return null; return null;
} }
/**
* Get the names of the default {@link TestExecutionListener} classes for
* this bootstrapper.
* <p>This method is invoked by {@link #getDefaultTestExecutionListenerClasses()}.
* @return an <em>unmodifiable</em> list of names of default {@code
* TestExecutionListener} classes
*/
protected abstract List<String> getDefaultTestExecutionListenerClassNames();
/** /**
* Determine the default {@link ContextLoader} class to use for the supplied * Determine the default {@link ContextLoader} class to use for the supplied
* test class. * test class.
@ -403,34 +435,19 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
protected abstract Class<? extends ContextLoader> getDefaultContextLoaderClass(Class<?> testClass); protected abstract Class<? extends ContextLoader> getDefaultContextLoaderClass(Class<?> testClass);
/** /**
* Build a {@link MergedContextConfiguration} instance from the supplied, * Process the supplied, newly instantiated {@link MergedContextConfiguration} instance.
* merged values. * <p>The returned {@link MergedContextConfiguration} instance may be a wrapper
* <p>Concrete subclasses typically will only need to instantiate * around or a replacement for the original.
* {@link MergedContextConfiguration} (or a specialized subclass thereof) * <p>The default implementation simply returns the supplied instance unmodified.
* from the provided values; further processing and merging of values is likely * <p>Concrete subclasses may choose to return a specialized subclass of
* unnecessary. * {@link MergedContextConfiguration} based on properties in the supplied instance.
* @param testClass the test class for which the {@code MergedContextConfiguration} * @param mergedConfig the {@code MergedContextConfiguration} to process;
* should be built (must not be {@code null}) * never {@code null}
* @param locations the merged resource locations * @return a fully initialized {@code MergedContextConfiguration}; never
* @param classes the merged annotated classes * {@code null}
* @param initializerClasses the merged context initializer classes
* @param activeProfiles the merged active bean definition profiles
* @param propertySourceLocations the merged {@code PropertySource} locations
* @param propertySourceProperties the merged {@code PropertySource} properties
* @param contextLoader the resolved {@code ContextLoader}
* @param cacheAwareContextLoaderDelegate the cache-aware context loader delegate
* to be provided to the instantiated {@code MergedContextConfiguration}
* @param parentConfig the merged context configuration for the parent application
* context in a context hierarchy, or {@code null} if there is no parent
* @return the fully initialized {@code MergedContextConfiguration}
*/ */
protected abstract MergedContextConfiguration buildMergedContextConfiguration( protected MergedContextConfiguration processMergedContextConfiguration(MergedContextConfiguration mergedConfig) {
Class<?> testClass, return mergedConfig;
String[] locations, }
Class<?>[] classes,
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses,
String[] activeProfiles, String[] propertySourceLocations, String[] propertySourceProperties,
ContextLoader contextLoader, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate,
MergedContextConfiguration parentConfig);
} }

17
spring-test/src/main/java/org/springframework/test/context/support/AbstractTestExecutionListener.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2014 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,6 +16,7 @@
package org.springframework.test.context.support; package org.springframework.test.context.support;
import org.springframework.core.Ordered;
import org.springframework.test.context.TestContext; import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestExecutionListener; import org.springframework.test.context.TestExecutionListener;
@ -28,7 +29,7 @@ import org.springframework.test.context.TestExecutionListener;
* @author Juergen Hoeller * @author Juergen Hoeller
* @since 2.5 * @since 2.5
*/ */
public abstract class AbstractTestExecutionListener implements TestExecutionListener { public abstract class AbstractTestExecutionListener implements TestExecutionListener, Ordered {
/** /**
* The default implementation is <em>empty</em>. Can be overridden by * The default implementation is <em>empty</em>. Can be overridden by
@ -75,4 +76,16 @@ public abstract class AbstractTestExecutionListener implements TestExecutionList
/* no-op */ /* no-op */
} }
/**
* The default implementation returns {@link Ordered#LOWEST_PRECEDENCE},
* thereby ensuring that custom listeners are ordered after default
* listeners supplied by the framework. Can be overridden by subclasses
* as necessary.
* @since 4.1
*/
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
} }

61
spring-test/src/main/java/org/springframework/test/context/support/DefaultTestContextBootstrapper.java

@ -16,60 +16,19 @@
package org.springframework.test.context.support; package org.springframework.test.context.support;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
import org.springframework.test.context.ContextLoader; import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContextBootstrapper; import org.springframework.test.context.TestContextBootstrapper;
import org.springframework.test.context.TestExecutionListener;
/** /**
* Default implementation of the {@link TestContextBootstrapper} SPI. * Default implementation of the {@link TestContextBootstrapper} SPI.
* *
* <ul> * <p>Uses {@link DelegatingSmartContextLoader} as the default {@link ContextLoader}.
* <li>Uses the following default {@link TestExecutionListener TestExecutionListeners}:
* <ol>
* <li>{@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener}
* <li>{@link org.springframework.test.context.support.DirtiesContextTestExecutionListener}
* <li>{@link org.springframework.test.context.transaction.TransactionalTestExecutionListener}
* <li>{@link org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener}
* </ol>
* <li>Uses {@link DelegatingSmartContextLoader} as the default {@link ContextLoader}.
* <li>Builds a standard {@link MergedContextConfiguration}.
* </ul>
* *
* @author Sam Brannen * @author Sam Brannen
* @since 4.1 * @since 4.1
*/ */
public class DefaultTestContextBootstrapper extends AbstractTestContextBootstrapper { public class DefaultTestContextBootstrapper extends AbstractTestContextBootstrapper {
private static final List<String> DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES = Collections.unmodifiableList(Arrays.asList(
"org.springframework.test.context.support.DependencyInjectionTestExecutionListener",
"org.springframework.test.context.support.DirtiesContextTestExecutionListener",
"org.springframework.test.context.transaction.TransactionalTestExecutionListener",
"org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener"));
/**
* Returns an unmodifiable list of fully qualified class names for the following
* default {@link TestExecutionListener TestExecutionListeners}:
* <ol>
* <li>{@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener}
* <li>{@link org.springframework.test.context.support.DirtiesContextTestExecutionListener}
* <li>{@link org.springframework.test.context.transaction.TransactionalTestExecutionListener}
* <li>{@link org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener}
* </ol>
*/
protected List<String> getDefaultTestExecutionListenerClassNames() {
return DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES;
}
/** /**
* Returns {@link DelegatingSmartContextLoader}. * Returns {@link DelegatingSmartContextLoader}.
*/ */
@ -78,22 +37,4 @@ public class DefaultTestContextBootstrapper extends AbstractTestContextBootstrap
return DelegatingSmartContextLoader.class; return DelegatingSmartContextLoader.class;
} }
/**
* Builds a standard {@link MergedContextConfiguration}.
*/
@Override
protected MergedContextConfiguration buildMergedContextConfiguration(
Class<?> testClass,
String[] locations,
Class<?>[] classes,
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses,
String[] activeProfiles, String[] propertySourceLocations, String[] propertySourceProperties,
ContextLoader contextLoader, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate,
MergedContextConfiguration parentConfig) {
return new MergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles,
propertySourceLocations, propertySourceProperties, contextLoader, cacheAwareContextLoaderDelegate,
parentConfig);
}
} }

13
spring-test/src/main/java/org/springframework/test/context/support/DependencyInjectionTestExecutionListener.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2014 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,7 +18,6 @@ package org.springframework.test.context.support;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.core.Conventions; import org.springframework.core.Conventions;
import org.springframework.test.context.TestContext; import org.springframework.test.context.TestContext;
@ -49,11 +48,19 @@ public class DependencyInjectionTestExecutionListener extends AbstractTestExecut
* <p>Permissible values include {@link Boolean#TRUE} and {@link Boolean#FALSE}. * <p>Permissible values include {@link Boolean#TRUE} and {@link Boolean#FALSE}.
*/ */
public static final String REINJECT_DEPENDENCIES_ATTRIBUTE = Conventions.getQualifiedAttributeName( public static final String REINJECT_DEPENDENCIES_ATTRIBUTE = Conventions.getQualifiedAttributeName(
DependencyInjectionTestExecutionListener.class, "reinjectDependencies"); DependencyInjectionTestExecutionListener.class, "reinjectDependencies");
private static final Log logger = LogFactory.getLog(DependencyInjectionTestExecutionListener.class); private static final Log logger = LogFactory.getLog(DependencyInjectionTestExecutionListener.class);
/**
* Returns {@code 2000}.
*/
@Override
public final int getOrder() {
return 2000;
}
/** /**
* Performs dependency injection on the * Performs dependency injection on the
* {@link TestContext#getTestInstance() test instance} of the supplied * {@link TestContext#getTestInstance() test instance} of the supplied

36
spring-test/src/main/java/org/springframework/test/context/support/DirtiesContextTestExecutionListener.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2014 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -48,20 +48,11 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi
/** /**
* Marks the {@linkplain ApplicationContext application context} of the supplied * Returns {@code 3000}.
* {@linkplain TestContext test context} as
* {@linkplain TestContext#markApplicationContextDirty(DirtiesContext.HierarchyMode) dirty}
* and sets {@link DependencyInjectionTestExecutionListener#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) { @Override
testContext.markApplicationContextDirty(hierarchyMode); public final int getOrder() {
testContext.setAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE, Boolean.TRUE); return 3000;
} }
/** /**
@ -132,4 +123,21 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi
} }
} }
/**
* Marks the {@linkplain ApplicationContext application context} of the supplied
* {@linkplain TestContext test context} as
* {@linkplain TestContext#markApplicationContextDirty(DirtiesContext.HierarchyMode) dirty}
* and sets {@link DependencyInjectionTestExecutionListener#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);
}
} }

8
spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java

@ -144,6 +144,14 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
private TransactionConfigurationAttributes configurationAttributes; private TransactionConfigurationAttributes configurationAttributes;
/**
* Returns {@code 4000}.
*/
@Override
public final int getOrder() {
return 4000;
}
/** /**
* If the test method of the supplied {@linkplain TestContext test context} * If the test method of the supplied {@linkplain TestContext test context}
* is configured to run within a transaction, this method will run * is configured to run within a transaction, this method will run

8
spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java

@ -87,6 +87,14 @@ public class ServletTestExecutionListener extends AbstractTestExecutionListener
private static final Log logger = LogFactory.getLog(ServletTestExecutionListener.class); private static final Log logger = LogFactory.getLog(ServletTestExecutionListener.class);
/**
* Returns {@code 1000}.
*/
@Override
public final int getOrder() {
return 1000;
}
/** /**
* Sets up thread-local state during the <em>test instance preparation</em> * Sets up thread-local state during the <em>test instance preparation</em>
* callback phase via Spring Web's {@link RequestContextHolder}, but only if * callback phase via Spring Web's {@link RequestContextHolder}, but only if

16
spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java

@ -84,8 +84,7 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
String[] activeProfiles, String resourceBasePath, ContextLoader contextLoader) { String[] activeProfiles, String resourceBasePath, ContextLoader contextLoader) {
this(testClass, locations, classes, contextInitializerClasses, activeProfiles, null, null, resourceBasePath, this(testClass, locations, classes, contextInitializerClasses, activeProfiles, null, null, resourceBasePath,
contextLoader, contextLoader, null, null);
null, null);
} }
/** /**
@ -122,6 +121,19 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
contextLoader, cacheAwareContextLoaderDelegate, parent); contextLoader, cacheAwareContextLoaderDelegate, parent);
} }
/**
* Create a new {@code WebMergedContextConfiguration} instance by copying
* all properties from the supplied {@code MergedContextConfiguration}.
* <p>If an <em>empty</em> value is supplied for the {@code resourceBasePath}
* an empty string will be used.
* @param resourceBasePath the resource path to the root directory of the web application
* @since 4.1
*/
public WebMergedContextConfiguration(MergedContextConfiguration mergedConfig, String resourceBasePath) {
super(mergedConfig);
this.resourceBasePath = !StringUtils.hasText(resourceBasePath) ? "" : resourceBasePath;
}
/** /**
* Create a new {@code WebMergedContextConfiguration} instance for the * Create a new {@code WebMergedContextConfiguration} instance for the
* supplied parameters. * supplied parameters.

57
spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java

@ -16,34 +16,21 @@
package org.springframework.test.context.web; package org.springframework.test.context.web;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
import org.springframework.test.context.ContextLoader; import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration; import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContextBootstrapper; import org.springframework.test.context.TestContextBootstrapper;
import org.springframework.test.context.TestExecutionListener;
import org.springframework.test.context.support.DefaultTestContextBootstrapper; import org.springframework.test.context.support.DefaultTestContextBootstrapper;
/** /**
* Web-specific implementation of the {@link TestContextBootstrapper} SPI. * Web-specific implementation of the {@link TestContextBootstrapper} SPI.
* *
* <ul> * <ul>
* <li>Prepends {@link org.springframework.test.context.web.ServletTestExecutionListener}
* to the list of default {@link TestExecutionListener TestExecutionListeners} supported by
* the superclass.
* <li>Uses {@link WebDelegatingSmartContextLoader} as the default {@link ContextLoader} * <li>Uses {@link WebDelegatingSmartContextLoader} as the default {@link ContextLoader}
* if the test class is annotated with {@link WebAppConfiguration @WebAppConfiguration} * if the test class is annotated with {@link WebAppConfiguration @WebAppConfiguration}
* and otherwise delegates to the superclass. * and otherwise delegates to the superclass.
* <li>Builds a {@link WebMergedContextConfiguration} if the test class is annotated * <li>Builds a {@link WebMergedContextConfiguration} if the test class is annotated
* with {@link WebAppConfiguration @WebAppConfiguration} and otherwise delegates to * with {@link WebAppConfiguration @WebAppConfiguration}.
* the superclass.
* </ul> * </ul>
* *
* @author Sam Brannen * @author Sam Brannen
@ -51,18 +38,6 @@ import org.springframework.test.context.support.DefaultTestContextBootstrapper;
*/ */
public class WebTestContextBootstrapper extends DefaultTestContextBootstrapper { public class WebTestContextBootstrapper extends DefaultTestContextBootstrapper {
/**
* Prepends {@link org.springframework.test.context.web.ServletTestExecutionListener}
* to the list of default {@link TestExecutionListener TestExecutionListeners}
* supported by the superclass and returns an unmodifiable, updated list.
*/
@Override
protected List<String> getDefaultTestExecutionListenerClassNames() {
List<String> classNames = new ArrayList<String>(super.getDefaultTestExecutionListenerClassNames());
classNames.add(0, "org.springframework.test.context.web.ServletTestExecutionListener");
return Collections.unmodifiableList(classNames);
}
/** /**
* Returns {@link WebDelegatingSmartContextLoader} if the supplied class is * Returns {@link WebDelegatingSmartContextLoader} if the supplied class is
* annotated with {@link WebAppConfiguration @WebAppConfiguration} and * annotated with {@link WebAppConfiguration @WebAppConfiguration} and
@ -79,33 +54,21 @@ public class WebTestContextBootstrapper extends DefaultTestContextBootstrapper {
} }
/** /**
* Builds a {@link WebMergedContextConfiguration} if the supplied class is * Returns a {@link WebMergedContextConfiguration} if the test class in the
* annotated with {@link WebAppConfiguration @WebAppConfiguration} and * supplied {@code MergedContextConfiguration} is annotated with
* otherwise delegates to the superclass. * {@link WebAppConfiguration @WebAppConfiguration} and otherwise returns
* the supplied instance unmodified.
*/ */
@Override @Override
protected MergedContextConfiguration buildMergedContextConfiguration( protected MergedContextConfiguration processMergedContextConfiguration(MergedContextConfiguration mergedConfig) {
Class<?> testClass, WebAppConfiguration webAppConfiguration = AnnotationUtils.findAnnotation(mergedConfig.getTestClass(),
String[] locations, WebAppConfiguration.class);
Class<?>[] classes,
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses,
String[] activeProfiles, String[] propertySourceLocations, String[] propertySourceProperties,
ContextLoader contextLoader, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate,
MergedContextConfiguration parentConfig) {
WebAppConfiguration webAppConfiguration = AnnotationUtils.findAnnotation(testClass, WebAppConfiguration.class);
if (webAppConfiguration != null) { if (webAppConfiguration != null) {
String resourceBasePath = webAppConfiguration.value(); return new WebMergedContextConfiguration(mergedConfig, webAppConfiguration.value());
return new WebMergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles,
propertySourceLocations, propertySourceProperties, resourceBasePath, contextLoader,
cacheAwareContextLoaderDelegate, parentConfig);
} }
// else... // else...
return super.buildMergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles, return mergedConfig;
propertySourceLocations, propertySourceProperties, contextLoader, cacheAwareContextLoaderDelegate,
parentConfig);
} }
} }

8
spring-test/src/main/resources/META-INF/spring.factories

@ -0,0 +1,8 @@
# Default TestExecutionListeners for the Spring TestContext Framework
#
org.springframework.test.context.TestExecutionListener = \
org.springframework.test.context.web.ServletTestExecutionListener,\
org.springframework.test.context.support.DependencyInjectionTestExecutionListener,\
org.springframework.test.context.support.DirtiesContextTestExecutionListener,\
org.springframework.test.context.transaction.TransactionalTestExecutionListener,\
org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener

2
spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java

@ -45,7 +45,7 @@ public class TestExecutionListenersTests {
@Test @Test
public void verifyNumDefaultListenersRegistered() throws Exception { public void verifyNumDefaultListenersRegistered() throws Exception {
TestContextManager testContextManager = new TestContextManager(DefaultListenersExampleTestCase.class); TestContextManager testContextManager = new TestContextManager(DefaultListenersExampleTestCase.class);
assertEquals("Num registered TELs for DefaultListenersExampleTestCase.", 4, assertEquals("Num registered TELs for DefaultListenersExampleTestCase.", 5,
testContextManager.getTestExecutionListeners().size()); testContextManager.getTestExecutionListeners().size());
} }

25
spring-test/src/test/java/org/springframework/test/context/jdbc/TransactionalSqlScriptsTests.java

@ -16,12 +16,19 @@
package org.springframework.test.context.jdbc; package org.springframework.test.context.jdbc;
import javax.sql.DataSource;
import org.junit.FixMethodOrder; import org.junit.FixMethodOrder;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters; import org.junit.runners.MethodSorters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.jdbc.JdbcTestUtils;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -31,11 +38,21 @@ import static org.junit.Assert.*;
* @author Sam Brannen * @author Sam Brannen
* @since 4.1 * @since 4.1
*/ */
@RunWith(SpringJUnit4ClassRunner.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING) @FixMethodOrder(MethodSorters.NAME_ASCENDING)
@ContextConfiguration(classes = EmptyDatabaseConfig.class) @ContextConfiguration(classes = EmptyDatabaseConfig.class)
@Transactional
@Sql({ "schema.sql", "data.sql" }) @Sql({ "schema.sql", "data.sql" })
@DirtiesContext @DirtiesContext
public class TransactionalSqlScriptsTests extends AbstractTransactionalJUnit4SpringContextTests { public class TransactionalSqlScriptsTests {
protected JdbcTemplate jdbcTemplate;
@Autowired
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Test @Test
// test##_ prefix is required for @FixMethodOrder. // test##_ prefix is required for @FixMethodOrder.
@ -50,6 +67,10 @@ public class TransactionalSqlScriptsTests extends AbstractTransactionalJUnit4Spr
assertNumUsers(2); assertNumUsers(2);
} }
protected int countRowsInTable(String tableName) {
return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
}
protected void assertNumUsers(int expected) { protected void assertNumUsers(int expected) {
assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user")); assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user"));
} }

2
spring-test/src/test/resources/META-INF/spring.factories

@ -0,0 +1,2 @@
# Test configuration file containing a non-existent default TestExecutionListener.
org.springframework.test.context.TestExecutionListener = org.example.FooListener
Loading…
Cancel
Save