提交 e6d16148 编写于 作者: S Sam Brannen

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
上级 e142fd11
......@@ -27,7 +27,7 @@ import java.util.Properties;
import org.apache.commons.logging.Log;
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.util.Assert;
import org.springframework.util.ClassUtils;
......@@ -48,12 +48,13 @@ import org.springframework.util.StringUtils;
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.2
*/
public abstract class SpringFactoriesLoader {
/** 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);
......@@ -61,7 +62,7 @@ public abstract class SpringFactoriesLoader {
/**
* Load the factory implementations of the given type from the default location,
* 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 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) {
result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
}
OrderComparator.sort(result);
AnnotationAwareOrderComparator.sort(result);
return result;
}
......
......@@ -79,6 +79,7 @@ public class MergedContextConfiguration implements Serializable {
private final String[] propertySourceProperties;
private final ContextLoader contextLoader;
private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate;
private final MergedContextConfiguration parent;
......@@ -185,6 +186,18 @@ public class MergedContextConfiguration implements Serializable {
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
* supplied parameters.
......
......@@ -63,8 +63,16 @@ public interface TestContextBootstrapper {
* for the test class in the {@link BootstrapContext} associated with this bootstrapper.
* <p>If {@link TestExecutionListeners @TestExecutionListeners} is not
* <em>present</em> on the test class in the {@code BootstrapContext},
* <em>default</em> listeners should be returned. Concrete implementations
* are free to determine what comprises the set of default listeners.
* <em>default</em> listeners should be returned. Furthermore, default
* 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}
* flag of {@link TestExecutionListeners @TestExecutionListeners} must be
* taken into consideration. Specifically, if the {@code inheritListeners}
......
......@@ -23,7 +23,13 @@ package org.springframework.test.context;
* <p>Concrete implementations must provide a {@code public} no-args constructor,
* so that listeners can be instantiated transparently by tools and configuration
* 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>
* <li>{@link org.springframework.test.context.web.ServletTestExecutionListener
* ServletTestExecutionListener}</li>
......
......@@ -89,6 +89,14 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
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
* {@link TestContext} <em>before</em> the current test method.
......
......@@ -31,7 +31,9 @@ import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.test.context.BootstrapContext;
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
import org.springframework.test.context.ContextConfiguration;
......@@ -55,11 +57,10 @@ import org.springframework.util.StringUtils;
* provides most of the behavior required by a bootstrapper.
*
* <p>Concrete subclasses typically will only need to provide implementations for
* the following {@code abstract} methods:
* the following methods:
* <ul>
* <li>{@link #getDefaultTestExecutionListenerClassNames}
* <li>{@link #getDefaultContextLoaderClass}
* <li>{@link #buildMergedContextConfiguration}
* <li>{@link #processMergedContextConfiguration}
* </ul>
*
* @author Sam Brannen
......@@ -98,6 +99,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
Class<?> clazz = getBootstrapContext().getTestClass();
Class<TestExecutionListeners> annotationType = TestExecutionListeners.class;
List<Class<? extends TestExecutionListener>> classesList = new ArrayList<Class<? extends TestExecutionListener>>();
boolean usingDefaults = false;
AnnotationDescriptor<TestExecutionListeners> descriptor = MetaAnnotationUtils.findAnnotationDescriptor(clazz,
annotationType);
......@@ -105,9 +107,10 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
// Use defaults?
if (descriptor == null) {
if (logger.isDebugEnabled()) {
logger.debug("@TestExecutionListeners is not present for class [" + clazz.getName()
+ "]: using defaults.");
logger.debug(String.format("@TestExecutionListeners is not present for class [%s]: using defaults.",
clazz.getName()));
}
usingDefaults = true;
classesList.addAll(getDefaultTestExecutionListenerClasses());
}
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());
for (Class<? extends TestExecutionListener> listenerClass : classesList) {
NoClassDefFoundError ncdfe = null;
......@@ -194,6 +211,28 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
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}
*/
......@@ -302,9 +341,11 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
String[] activeProfiles = ActiveProfilesUtils.resolveActiveProfiles(testClass);
MergedTestPropertySources mergedTestPropertySources = TestPropertySourceUtils.buildMergedTestPropertySources(testClass);
return buildMergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles,
mergedTestPropertySources.getLocations(), mergedTestPropertySources.getProperties(), contextLoader,
cacheAwareContextLoaderDelegate, parentConfig);
MergedContextConfiguration mergedConfig = new MergedContextConfiguration(testClass, locations, classes,
initializerClasses, activeProfiles, mergedTestPropertySources.getLocations(),
mergedTestPropertySources.getProperties(), contextLoader, cacheAwareContextLoaderDelegate, parentConfig);
return processMergedContextConfiguration(mergedConfig);
}
/**
......@@ -383,15 +424,6 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
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
* test class.
......@@ -403,34 +435,19 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
protected abstract Class<? extends ContextLoader> getDefaultContextLoaderClass(Class<?> testClass);
/**
* Build a {@link MergedContextConfiguration} instance from the supplied,
* merged values.
* <p>Concrete subclasses typically will only need to instantiate
* {@link MergedContextConfiguration} (or a specialized subclass thereof)
* from the provided values; further processing and merging of values is likely
* unnecessary.
* @param testClass the test class for which the {@code MergedContextConfiguration}
* should be built (must not be {@code null})
* @param locations the merged resource locations
* @param classes the merged annotated classes
* @param initializerClasses the merged context initializer classes
* @param activeProfiles the merged active bean definition profiles
* @param 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}
* Process the supplied, newly instantiated {@link MergedContextConfiguration} instance.
* <p>The returned {@link MergedContextConfiguration} instance may be a wrapper
* around or a replacement for the original.
* <p>The default implementation simply returns the supplied instance unmodified.
* <p>Concrete subclasses may choose to return a specialized subclass of
* {@link MergedContextConfiguration} based on properties in the supplied instance.
* @param mergedConfig the {@code MergedContextConfiguration} to process;
* never {@code null}
* @return a fully initialized {@code MergedContextConfiguration}; never
* {@code null}
*/
protected abstract 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);
protected MergedContextConfiguration processMergedContextConfiguration(MergedContextConfiguration mergedConfig) {
return mergedConfig;
}
}
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -16,6 +16,7 @@
package org.springframework.test.context.support;
import org.springframework.core.Ordered;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestExecutionListener;
......@@ -28,7 +29,7 @@ import org.springframework.test.context.TestExecutionListener;
* @author Juergen Hoeller
* @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
......@@ -75,4 +76,16 @@ public abstract class AbstractTestExecutionListener implements TestExecutionList
/* 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;
}
}
......@@ -16,60 +16,19 @@
package org.springframework.test.context.support;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContextBootstrapper;
import org.springframework.test.context.TestExecutionListener;
/**
* Default implementation of the {@link TestContextBootstrapper} SPI.
*
* <ul>
* <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>
* <p>Uses {@link DelegatingSmartContextLoader} as the default {@link ContextLoader}.
*
* @author Sam Brannen
* @since 4.1
*/
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}.
*/
......@@ -78,22 +37,4 @@ public class DefaultTestContextBootstrapper extends AbstractTestContextBootstrap
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);
}
}
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -18,7 +18,6 @@ package org.springframework.test.context.support;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.core.Conventions;
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}.
*/
public static final String REINJECT_DEPENDENCIES_ATTRIBUTE = Conventions.getQualifiedAttributeName(
DependencyInjectionTestExecutionListener.class, "reinjectDependencies");
DependencyInjectionTestExecutionListener.class, "reinjectDependencies");
private static final Log logger = LogFactory.getLog(DependencyInjectionTestExecutionListener.class);
/**
* Returns {@code 2000}.
*/
@Override
public final int getOrder() {
return 2000;
}
/**
* Performs dependency injection on the
* {@link TestContext#getTestInstance() test instance} of the supplied
......
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -48,20 +48,11 @@ 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
* Returns {@code 3000}.
*/
protected void dirtyContext(TestContext testContext, HierarchyMode hierarchyMode) {
testContext.markApplicationContextDirty(hierarchyMode);
testContext.setAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE, Boolean.TRUE);
@Override
public final int getOrder() {
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);
}
}
......@@ -144,6 +144,14 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
private TransactionConfigurationAttributes configurationAttributes;
/**
* Returns {@code 4000}.
*/
@Override
public final int getOrder() {
return 4000;
}
/**
* If the test method of the supplied {@linkplain TestContext test context}
* is configured to run within a transaction, this method will run
......
......@@ -87,6 +87,14 @@ public class ServletTestExecutionListener extends AbstractTestExecutionListener
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>
* callback phase via Spring Web's {@link RequestContextHolder}, but only if
......
......@@ -84,8 +84,7 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
String[] activeProfiles, String resourceBasePath, ContextLoader contextLoader) {
this(testClass, locations, classes, contextInitializerClasses, activeProfiles, null, null, resourceBasePath,
contextLoader,
null, null);
contextLoader, null, null);
}
/**
......@@ -122,6 +121,19 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
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
* supplied parameters.
......
......@@ -16,34 +16,21 @@
package org.springframework.test.context.web;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContextBootstrapper;
import org.springframework.test.context.TestExecutionListener;
import org.springframework.test.context.support.DefaultTestContextBootstrapper;
/**
* Web-specific implementation of the {@link TestContextBootstrapper} SPI.
*
* <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}
* if the test class is annotated with {@link WebAppConfiguration @WebAppConfiguration}
* and otherwise delegates to the superclass.
* <li>Builds a {@link WebMergedContextConfiguration} if the test class is annotated
* with {@link WebAppConfiguration @WebAppConfiguration} and otherwise delegates to
* the superclass.
* with {@link WebAppConfiguration @WebAppConfiguration}.
* </ul>
*
* @author Sam Brannen
......@@ -51,18 +38,6 @@ import org.springframework.test.context.support.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
* annotated with {@link WebAppConfiguration @WebAppConfiguration} and
......@@ -79,33 +54,21 @@ public class WebTestContextBootstrapper extends DefaultTestContextBootstrapper {
}
/**
* Builds a {@link WebMergedContextConfiguration} if the supplied class is
* annotated with {@link WebAppConfiguration @WebAppConfiguration} and
* otherwise delegates to the superclass.
* Returns a {@link WebMergedContextConfiguration} if the test class in the
* supplied {@code MergedContextConfiguration} is annotated with
* {@link WebAppConfiguration @WebAppConfiguration} and otherwise returns
* the supplied instance unmodified.
*/
@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) {
WebAppConfiguration webAppConfiguration = AnnotationUtils.findAnnotation(testClass, WebAppConfiguration.class);
protected MergedContextConfiguration processMergedContextConfiguration(MergedContextConfiguration mergedConfig) {
WebAppConfiguration webAppConfiguration = AnnotationUtils.findAnnotation(mergedConfig.getTestClass(),
WebAppConfiguration.class);
if (webAppConfiguration != null) {
String resourceBasePath = webAppConfiguration.value();
return new WebMergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles,
propertySourceLocations, propertySourceProperties, resourceBasePath, contextLoader,
cacheAwareContextLoaderDelegate, parentConfig);
return new WebMergedContextConfiguration(mergedConfig, webAppConfiguration.value());
}
// else...
return super.buildMergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles,
propertySourceLocations, propertySourceProperties, contextLoader, cacheAwareContextLoaderDelegate,
parentConfig);
return mergedConfig;
}
}
# 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
......@@ -45,7 +45,7 @@ public class TestExecutionListenersTests {
@Test
public void verifyNumDefaultListenersRegistered() throws Exception {
TestContextManager testContextManager = new TestContextManager(DefaultListenersExampleTestCase.class);
assertEquals("Num registered TELs for DefaultListenersExampleTestCase.", 4,
assertEquals("Num registered TELs for DefaultListenersExampleTestCase.", 5,
testContextManager.getTestExecutionListeners().size());
}
......
......@@ -16,12 +16,19 @@
package org.springframework.test.context.jdbc;
import javax.sql.DataSource;
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.jdbc.core.JdbcTemplate;
import org.springframework.test.annotation.DirtiesContext;
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.*;
......@@ -31,11 +38,21 @@ import static org.junit.Assert.*;
* @author Sam Brannen
* @since 4.1
*/
@RunWith(SpringJUnit4ClassRunner.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@ContextConfiguration(classes = EmptyDatabaseConfig.class)
@Transactional
@Sql({ "schema.sql", "data.sql" })
@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##_ prefix is required for @FixMethodOrder.
......@@ -50,6 +67,10 @@ public class TransactionalSqlScriptsTests extends AbstractTransactionalJUnit4Spr
assertNumUsers(2);
}
protected int countRowsInTable(String tableName) {
return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
}
protected void assertNumUsers(int expected) {
assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user"));
}
......
# Test configuration file containing a non-existent default TestExecutionListener.
org.springframework.test.context.TestExecutionListener = org.example.FooListener
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册