Merge pull request #557 from sbrannen/SPR-7655

Introduce annotation to execute SQL scripts in the TCF
This commit is contained in:
Sam Brannen
2014-06-05 20:12:45 +02:00
37 changed files with 1912 additions and 128 deletions

View File

@@ -26,6 +26,7 @@ import javax.sql.DataSource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Populates, initializes, or cleans up a database using SQL scripts defined in
@@ -95,7 +96,7 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
* @param ignoreFailedDrops flag to indicate that a failed SQL {@code DROP}
* statement can be ignored
* @param sqlScriptEncoding the encoding for the supplied SQL scripts, if
* different from the platform encoding; may be {@code null}
* different from the platform encoding; may be {@code null} or empty
* @param scripts the scripts to execute to initialize or populate the database;
* never {@code null}
* @since 4.0.3
@@ -105,7 +106,7 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
this(scripts);
this.continueOnError = continueOnError;
this.ignoreFailedDrops = ignoreFailedDrops;
this.sqlScriptEncoding = sqlScriptEncoding;
setSqlScriptEncoding(sqlScriptEncoding);
}
/**
@@ -143,7 +144,7 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
* @see #addScript(Resource)
*/
public void setSqlScriptEncoding(String sqlScriptEncoding) {
this.sqlScriptEncoding = sqlScriptEncoding;
this.sqlScriptEncoding = StringUtils.hasText(sqlScriptEncoding) ? sqlScriptEncoding : null;
}
/**

View File

@@ -0,0 +1,244 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.context.jdbc;
import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import org.springframework.util.ResourceUtils;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
/**
* {@code @DatabaseInitializer} is used to annotate a test class or test method
* to configure SQL scripts to be executed against a given database during
* integration tests.
*
* <p>Method-level declarations override class-level declarations.
*
* <p>Script execution is performed by the {@link DatabaseInitializerTestExecutionListener},
* which is enabled by default.
*
* <p>The configuration options provided by this annotation are a superset of
* those provided by the {@code <jdbc:initialize-database />} XML namespace
* element. Consult the Javadoc of individual attributes for details.
*
* <p>Beginning with Java 8, {@code @DatabaseInitializer} can be used as a
* <em>{@linkplain Repeatable repeatable}</em> annotation. Otherwise,
* {@link DatabaseInitializers @DatabaseInitializers} can be used as an explicit
* container for declaring multiple instances of {@code @DatabaseInitializer}.
*
* <p>This annotation may be used as a <em>meta-annotation</em> to create custom
* <em>composed annotations</em>; however, attribute overrides are not currently
* supported for {@linkplain Repeatable repeatable} annotations that are used as
* meta-annotations.
*
* @author Sam Brannen
* @author Tadaya Tsuyukubo
* @since 4.1
* @see DatabaseInitializers
* @see DatabaseInitializerTestExecutionListener
* @see org.springframework.transaction.annotation.Transactional
* @see org.springframework.test.context.transaction.TransactionalTestExecutionListener
* @see org.springframework.jdbc.datasource.init.ResourceDatabasePopulator
* @see org.springframework.jdbc.datasource.init.ScriptUtils
*/
@Documented
@Inherited
@Retention(RUNTIME)
@Target({ TYPE, METHOD })
@Repeatable(DatabaseInitializers.class)
public @interface DatabaseInitializer {
/**
* Enumeration of <em>phases</em> that dictate when SQL scripts are executed.
*/
static enum ExecutionPhase {
/**
* The configured SQL scripts will be executed <em>before</em> the
* corresponding test method.
*/
BEFORE_TEST_METHOD,
/**
* The configured SQL scripts will be executed <em>after</em> the
* corresponding test method.
*/
AFTER_TEST_METHOD;
}
/**
* Alias for {@link #scripts}.
* <p>This attribute may <strong>not</strong> be used in conjunction with
* {@link #scripts}, but it may be used instead of {@link #scripts}.
*/
String[] value() default {};
/**
* The paths to the SQL scripts to execute.
*
* <p>This attribute may <strong>not</strong> be used in conjunction with
* {@link #value}, but it may be used instead of {@link #value}.
*
* <h3>Path Resource Semantics</h3>
* <p>Each path will be interpreted as a Spring
* {@link org.springframework.core.io.Resource Resource}. A plain path
* &mdash; for example, {@code "schema.sql"} &mdash; will be treated as a
* classpath resource that is <em>relative</em> to the package in which the
* test class is defined. A path starting with a slash will be treated as an
* <em>absolute</em> classpath resource, for example:
* {@code "/org/example/schema.sql"}. A path which references a
* URL (e.g., a path prefixed with
* {@link ResourceUtils#CLASSPATH_URL_PREFIX classpath:},
* {@link ResourceUtils#FILE_URL_PREFIX file:}, {@code http:}, etc.) will be
* loaded using the specified resource protocol.
*
* <h3>Default Script Detection</h3>
* <p>If no SQL scripts are specified, an attempt will be made to detect a
* <em>default</em> script depending on where this annotation is declared.
* If a default cannot be detected, an {@link IllegalStateException} will be
* thrown.
* <ul>
* <li><strong>class-level declaration</strong>: if the annotated test class
* is {@code com.example.MyTest}, the corresponding default script is
* {@code "classpath:com/example/MyTest.sql"}.</li>
* <li><strong>method-level declaration</strong>: if the annotated test
* method is named {@code testMethod()} and is defined in the class
* {@code com.example.MyTest}, the corresponding default script is
* {@code "classpath:com/example/MyTest.testMethod.sql"}.</li>
* </ul>
*/
String[] scripts() default {};
/**
* The encoding for the supplied SQL scripts, if different from the platform
* encoding.
* <p>An empty string denotes that the platform encoding should be used.
*/
String encoding() default "";
/**
* The bean name of the {@link javax.sql.DataSource} against which the scripts
* should be executed.
* <p>The name is only used if there is more than one bean of type
* {@code DataSource} in the test's {@code ApplicationContext}. If there is
* only one such bean, it is not necessary to specify a bean name.
* <p>Defaults to an empty string, requiring that one of the following is
* true:
* <ol>
* <li>There is only one bean of type {@code DataSource} in the test's
* {@code ApplicationContext}.</li>
* <li>The {@code DataSource} to use is named {@code "dataSource"}.</li>
* </ol>
*/
String dataSource() default "";
/**
* The bean name of the {@link org.springframework.transaction.PlatformTransactionManager
* PlatformTransactionManager} that should be used to drive transactions.
* <p>The name is only used if there is more than one bean of type
* {@code PlatformTransactionManager} in the test's {@code ApplicationContext}.
* If there is only one such bean, it is not necessary to specify a bean name.
* <p>Defaults to an empty string, requiring that one of the following is
* true:
* <ol>
* <li>There is only one bean of type {@code PlatformTransactionManager} in
* the test's {@code ApplicationContext}.</li>
* <li>{@link org.springframework.transaction.annotation.TransactionManagementConfigurer
* TransactionManagementConfigurer} has been implemented to specify which
* {@code PlatformTransactionManager} bean should be used for annotation-driven
* transaction management.</li>
* <li>The {@code PlatformTransactionManager} to use is named
* {@code "transactionManager"}.</li>
* </ol>
*/
String transactionManager() default "";
/**
* Flag to indicate that the SQL scripts must be executed in a new transaction.
* <p>Defaults to {@code false}, meaning that the SQL scripts will be executed
* within the current transaction if present. The <em>current</em> transaction
* will typically be managed by the
* {@link org.springframework.test.context.transaction.TransactionalTestExecutionListener
* TransactionalTestExecutionListener}.
* <p>Can be set to {@code true} to ensure that the scripts are executed in
* a new, isolated transaction that will be immediately committed.
*/
boolean requireNewTransaction() default false;
/**
* The character string used to separate individual statements within the
* SQL scripts.
* <p>Defaults to {@code ";"} if not specified and falls back to {@code "\n"}
* as a last resort; may be set to {@link ScriptUtils#EOF_STATEMENT_SEPARATOR}
* to signal that each script contains a single statement without a separator.
*/
String separator() default ScriptUtils.DEFAULT_STATEMENT_SEPARATOR;
/**
* The prefix that identifies single-line comments within the SQL scripts.
* <p>Defaults to {@code "--"}.
*/
String commentPrefix() default ScriptUtils.DEFAULT_COMMENT_PREFIX;
/**
* The start delimiter that identifies block comments within the SQL scripts.
* <p>Defaults to {@code "/*"}.
* @see #blockCommentEndDelimiter
*/
String blockCommentStartDelimiter() default ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER;
/**
* The end delimiter that identifies block comments within the SQL scripts.
* <p>Defaults to <code>"*&#47;"</code>.
* @see #blockCommentStartDelimiter
*/
String blockCommentEndDelimiter() default ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER;
/**
* Flag to indicate that all failures in SQL should be logged but not cause
* a failure.
* <p>Defaults to {@code false}.
* @see #ignoreFailedDrops
*/
boolean continueOnError() default false;
/**
* Flag to indicate that a failed SQL {@code DROP} statement can be ignored.
* <p>This is useful for a non-embedded database whose SQL dialect does not
* support an {@code IF EXISTS} clause in a {@code DROP} statement.
* <p>The default is {@code false} so that if a script is accidentally
* executed, it will fail fast if the script starts with a {@code DROP}
* statement.
* @see #continueOnError
*/
boolean ignoreFailedDrops() default false;
/**
* When the SQL scripts should be executed.
* <p>Defaults to {@link ExecutionPhase#BEFORE_TEST_METHOD BEFORE_TEST_METHOD}.
*/
ExecutionPhase executionPhase() default ExecutionPhase.BEFORE_TEST_METHOD;
}

View File

@@ -0,0 +1,244 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.context.jdbc;
import java.lang.reflect.Method;
import java.util.Set;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.jdbc.DatabaseInitializer.ExecutionPhase;
import org.springframework.test.context.support.AbstractTestExecutionListener;
import org.springframework.test.context.transaction.TestContextTransactionUtils;
import org.springframework.test.context.util.TestContextResourceUtils;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ResourceUtils;
/**
* {@code TestExecutionListener} that provides support for executing SQL scripts
* configured via the {@link DatabaseInitializer @DatabaseInitializer} annotation.
*
* <p>SQL scripts will be executed {@linkplain #beforeTestMethod(TestContext) before}
* or {@linkplain #afterTestMethod(TestContext) after} execution of the corresponding
* {@linkplain java.lang.reflect.Method test method}, depending on the configured
* value of the {@link DatabaseInitializer#requireNewTransaction requireNewTransaction}
* flag.
*
* <h3>Script Resources</h3>
* <p>For details on default script detection and how explicit script locations
* are interpreted, see {@link DatabaseInitializer#scripts}.
*
* <h3>Required Spring Beans</h3>
* <p>A {@link DataSource} and {@link PlatformTransactionManager} must be defined
* as beans in the Spring {@link ApplicationContext} for the corresponding test.
* Consult the Javadoc for {@link TestContextTransactionUtils#retrieveDataSource}
* and {@link TestContextTransactionUtils#retrieveTransactionManager} for details
* on the algorithms used to locate these beans.
*
* @author Sam Brannen
* @since 4.1
* @see DatabaseInitializer
* @see DatabaseInitializers
* @see org.springframework.test.context.transaction.TransactionalTestExecutionListener
* @see org.springframework.jdbc.datasource.init.ResourceDatabasePopulator
* @see org.springframework.jdbc.datasource.init.ScriptUtils
*/
public class DatabaseInitializerTestExecutionListener extends AbstractTestExecutionListener {
private static final Log logger = LogFactory.getLog(DatabaseInitializerTestExecutionListener.class);
/**
* Execute SQL scripts configured via {@link DatabaseInitializer @DatabaseInitializer}
* for the supplied {@link TestContext} <em>before</em> the current test method.
*/
@Override
public void beforeTestMethod(TestContext testContext) throws Exception {
executeDatabaseInitializers(testContext, ExecutionPhase.BEFORE_TEST_METHOD);
}
/**
* Execute SQL scripts configured via {@link DatabaseInitializer @DatabaseInitializer}
* for the supplied {@link TestContext} <em>after</em> the current test method.
*/
@Override
public void afterTestMethod(TestContext testContext) throws Exception {
executeDatabaseInitializers(testContext, ExecutionPhase.AFTER_TEST_METHOD);
}
/**
* Execute SQL scripts configured via {@link DatabaseInitializer @DatabaseInitializer}
* for the supplied {@link TestContext} and {@link ExecutionPhase}.
*/
private void executeDatabaseInitializers(TestContext testContext, ExecutionPhase executionPhase) throws Exception {
boolean classLevel = false;
Set<DatabaseInitializer> databaseInitializers = AnnotationUtils.getRepeatableAnnotation(
testContext.getTestMethod(), DatabaseInitializers.class, DatabaseInitializer.class);
if (databaseInitializers.isEmpty()) {
databaseInitializers = AnnotationUtils.getRepeatableAnnotation(testContext.getTestClass(),
DatabaseInitializers.class, DatabaseInitializer.class);
if (!databaseInitializers.isEmpty()) {
classLevel = true;
}
}
for (DatabaseInitializer databaseInitializer : databaseInitializers) {
executeDatabaseInitializer(databaseInitializer, executionPhase, testContext, classLevel);
}
}
/**
* Execute the SQL scripts configured via the supplied
* {@link DatabaseInitializer @DatabaseInitializer} for the given
* {@link ExecutionPhase} and {@link TestContext}.
*
* <p>Special care must be taken in order to properly support the
* {@link DatabaseInitializer#requireNewTransaction requireNewTransaction}
* flag.
*
* @param databaseInitializer the {@code @DatabaseInitializer} to parse
* @param executionPhase the current execution phase
* @param testContext the current {@code TestContext}
* @param classLevel {@code true} if {@link DatabaseInitializer @DatabaseInitializer}
* was declared at the class level
*/
@SuppressWarnings("serial")
private void executeDatabaseInitializer(DatabaseInitializer databaseInitializer, ExecutionPhase executionPhase,
TestContext testContext, boolean classLevel) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Processing %s for execution phase [%s] and test context %s.",
databaseInitializer, executionPhase, testContext));
}
if (executionPhase != databaseInitializer.executionPhase()) {
return;
}
final ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.setSqlScriptEncoding(databaseInitializer.encoding());
populator.setSeparator(databaseInitializer.separator());
populator.setCommentPrefix(databaseInitializer.commentPrefix());
populator.setBlockCommentStartDelimiter(databaseInitializer.blockCommentStartDelimiter());
populator.setBlockCommentEndDelimiter(databaseInitializer.blockCommentEndDelimiter());
populator.setContinueOnError(databaseInitializer.continueOnError());
populator.setIgnoreFailedDrops(databaseInitializer.ignoreFailedDrops());
String[] scripts = getScripts(databaseInitializer, testContext, classLevel);
scripts = TestContextResourceUtils.convertToClasspathResourcePaths(testContext.getTestClass(), scripts);
populator.setScripts(TestContextResourceUtils.convertToResources(testContext.getApplicationContext(), scripts));
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL scripts: " + ObjectUtils.nullSafeToString(scripts));
}
final DataSource dataSource = TestContextTransactionUtils.retrieveDataSource(testContext,
databaseInitializer.dataSource());
final PlatformTransactionManager transactionManager = TestContextTransactionUtils.retrieveTransactionManager(
testContext, databaseInitializer.transactionManager());
int propagation = databaseInitializer.requireNewTransaction() ? TransactionDefinition.PROPAGATION_REQUIRES_NEW
: TransactionDefinition.PROPAGATION_REQUIRED;
TransactionAttribute transactionAttribute = TestContextTransactionUtils.createDelegatingTransactionAttribute(
testContext, new DefaultTransactionAttribute(propagation));
new TransactionTemplate(transactionManager, transactionAttribute).execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
populator.execute(dataSource);
};
});
}
private String[] getScripts(DatabaseInitializer databaseInitializer, TestContext testContext, boolean classLevel) {
String[] scripts = databaseInitializer.scripts();
String[] value = databaseInitializer.value();
boolean scriptsDeclared = !ObjectUtils.isEmpty(scripts);
boolean valueDeclared = !ObjectUtils.isEmpty(value);
if (valueDeclared && scriptsDeclared) {
String elementType = (classLevel ? "class" : "method");
String elementName = (classLevel ? testContext.getTestClass().getName()
: testContext.getTestMethod().toString());
String msg = String.format("Test %s [%s] has been configured with @DatabaseInitializer's 'value' [%s] "
+ "and 'scripts' [%s] attributes. Only one declaration of SQL script "
+ "paths is permitted per @DatabaseInitializer annotation.", elementType, elementName,
ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(scripts));
logger.error(msg);
throw new IllegalStateException(msg);
}
if (valueDeclared) {
scripts = value;
}
if (ObjectUtils.isEmpty(scripts)) {
scripts = new String[] { detectDefaultScript(testContext, classLevel) };
}
return scripts;
}
/**
* Detect a default SQL script by implementing the algorithm defined in
* {@link DatabaseInitializer#scripts}.
*/
private String detectDefaultScript(TestContext testContext, boolean classLevel) {
Class<?> clazz = testContext.getTestClass();
Method method = testContext.getTestMethod();
String elementType = (classLevel ? "class" : "method");
String elementName = (classLevel ? clazz.getName() : method.toString());
String resourcePath = ClassUtils.convertClassNameToResourcePath(clazz.getName());
if (!classLevel) {
resourcePath += "." + method.getName();
}
resourcePath += ".sql";
String prefixedResourcePath = ResourceUtils.CLASSPATH_URL_PREFIX + resourcePath;
ClassPathResource classPathResource = new ClassPathResource(resourcePath);
if (classPathResource.exists()) {
if (logger.isInfoEnabled()) {
logger.info(String.format("Detected default SQL script \"%s\" for test %s [%s]", prefixedResourcePath,
elementType, elementName));
}
return prefixedResourcePath;
}
else {
String msg = String.format("Could not detect default SQL script for test %s [%s]: "
+ "%s does not exist. Either declare scripts via @DatabaseInitializer or make the "
+ "default SQL script available.", elementType, elementName, classPathResource);
logger.error(msg);
throw new IllegalStateException(msg);
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.context.jdbc;
import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
/**
* Container annotation that aggregates several {@link DatabaseInitializer}
* annotations.
*
* <p>Can be used natively, declaring several nested {@link DatabaseInitializer}
* annotations. Can also be used in conjunction with Java 8's support for
* repeatable annotations, where {@code @DatabaseInitializer} can simply be
* declared several times on the same class or method, implicitly generating
* this container annotation.
*
* <p>This annotation may be used as a <em>meta-annotation</em> to create custom
* <em>composed annotations</em>.
*
* @author Sam Brannen
* @since 4.1
* @see DatabaseInitializer
*/
@Documented
@Inherited
@Retention(RUNTIME)
@Target({ TYPE, METHOD })
public @interface DatabaseInitializers {
DatabaseInitializer[] value();
}

View File

@@ -26,6 +26,7 @@ import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.jdbc.DatabaseInitializerTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.jdbc.JdbcTestUtils;
import org.springframework.transaction.PlatformTransactionManager;
@@ -63,6 +64,7 @@ import org.springframework.transaction.annotation.Transactional;
* @see org.springframework.test.context.ContextConfiguration
* @see org.springframework.test.context.TestExecutionListeners
* @see org.springframework.test.context.transaction.TransactionalTestExecutionListener
* @see org.springframework.test.context.jdbc.DatabaseInitializerTestExecutionListener
* @see org.springframework.test.context.transaction.TransactionConfiguration
* @see org.springframework.transaction.annotation.Transactional
* @see org.springframework.test.annotation.Rollback
@@ -71,7 +73,7 @@ import org.springframework.transaction.annotation.Transactional;
* @see org.springframework.test.jdbc.JdbcTestUtils
* @see org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests
*/
@TestExecutionListeners(TransactionalTestExecutionListener.class)
@TestExecutionListeners({ TransactionalTestExecutionListener.class, DatabaseInitializerTestExecutionListener.class })
@Transactional
public abstract class AbstractTransactionalJUnit4SpringContextTests extends AbstractJUnit4SpringContextTests {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,6 @@ import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
@@ -30,16 +29,15 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.SmartContextLoader;
import org.springframework.test.context.util.TestContextResourceUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
/**
* Abstract application context loader that provides a basis for all concrete
@@ -63,8 +61,6 @@ public abstract class AbstractContextLoader implements SmartContextLoader {
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static final String SLASH = "/";
private static final Log logger = LogFactory.getLog(AbstractContextLoader.class);
@@ -87,7 +83,8 @@ public abstract class AbstractContextLoader implements SmartContextLoader {
*/
@Override
public void processContextConfiguration(ContextConfigurationAttributes configAttributes) {
String[] processedLocations = processLocations(configAttributes.getDeclaringClass(), configAttributes.getLocations());
String[] processedLocations = processLocations(configAttributes.getDeclaringClass(),
configAttributes.getLocations());
configAttributes.setLocations(processedLocations);
}
@@ -118,25 +115,23 @@ public abstract class AbstractContextLoader implements SmartContextLoader {
protected void prepareContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
context.getEnvironment().setActiveProfiles(mergedConfig.getActiveProfiles());
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses =
mergedConfig.getContextInitializerClasses();
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses = mergedConfig.getContextInitializerClasses();
if (initializerClasses.isEmpty()) {
// no ApplicationContextInitializers have been declared -> nothing to do
return;
}
List<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerInstances =
new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>();
List<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerInstances = new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>();
Class<?> contextClass = context.getClass();
for (Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>> initializerClass : initializerClasses) {
Class<?> initializerContextClass = GenericTypeResolver.resolveTypeArgument(initializerClass,
ApplicationContextInitializer.class);
ApplicationContextInitializer.class);
Assert.isAssignable(initializerContextClass, contextClass, String.format(
"Could not add context initializer [%s] since its generic parameter [%s] " +
"is not assignable from the type of application context used by this " +
"context loader [%s]: ", initializerClass.getName(), initializerContextClass.getName(),
contextClass.getName()));
"Could not add context initializer [%s] since its generic parameter [%s] "
+ "is not assignable from the type of application context used by this "
+ "context loader [%s]: ", initializerClass.getName(), initializerContextClass.getName(),
contextClass.getName()));
initializerInstances.add((ApplicationContextInitializer<ConfigurableApplicationContext>) BeanUtils.instantiateClass(initializerClass));
}
@@ -171,8 +166,8 @@ public abstract class AbstractContextLoader implements SmartContextLoader {
*/
@Override
public final String[] processLocations(Class<?> clazz, String... locations) {
return (ObjectUtils.isEmpty(locations) && isGenerateDefaultLocations()) ?
generateDefaultLocations(clazz) : modifyLocations(clazz, locations);
return (ObjectUtils.isEmpty(locations) && isGenerateDefaultLocations()) ? generateDefaultLocations(clazz)
: modifyLocations(clazz, locations);
}
/**
@@ -180,7 +175,7 @@ public abstract class AbstractContextLoader implements SmartContextLoader {
* supplied class.
* <p>For example, if the supplied class is {@code com.example.MyTest},
* the generated locations will contain a single string with a value of
* &quot;classpath:/com/example/MyTest{@code <suffix>}&quot;,
* &quot;classpath:com/example/MyTest{@code <suffix>}&quot;,
* where {@code <suffix>} is the value of the
* {@link #getResourceSuffix() resource suffix} string.
* <p>As of Spring 3.1, the implementation of this method adheres to the
@@ -199,21 +194,21 @@ public abstract class AbstractContextLoader implements SmartContextLoader {
Assert.notNull(clazz, "Class must not be null");
String suffix = getResourceSuffix();
Assert.hasText(suffix, "Resource suffix must not be empty");
String resourcePath = SLASH + ClassUtils.convertClassNameToResourcePath(clazz.getName()) + suffix;
String resourcePath = ClassUtils.convertClassNameToResourcePath(clazz.getName()) + suffix;
String prefixedResourcePath = ResourceUtils.CLASSPATH_URL_PREFIX + resourcePath;
ClassPathResource classPathResource = new ClassPathResource(resourcePath, clazz);
ClassPathResource classPathResource = new ClassPathResource(resourcePath);
if (classPathResource.exists()) {
if (logger.isInfoEnabled()) {
logger.info(String.format("Detected default resource location \"%s\" for test class [%s]",
prefixedResourcePath, clazz.getName()));
prefixedResourcePath, clazz.getName()));
}
return new String[] {prefixedResourcePath};
return new String[] { prefixedResourcePath };
}
else {
if (logger.isInfoEnabled()) {
logger.info(String.format("Could not detect default resource locations for test class [%s]: " +
"%s does not exist", clazz.getName(), classPathResource));
logger.info(String.format("Could not detect default resource locations for test class [%s]: "
+ "%s does not exist", clazz.getName(), classPathResource));
}
return EMPTY_STRING_ARRAY;
}
@@ -221,15 +216,8 @@ public abstract class AbstractContextLoader implements SmartContextLoader {
/**
* Generate a modified version of the supplied locations array and return it.
* <p>A plain path &mdash; for example, &quot;context.xml&quot; &mdash; will
* be treated as a classpath resource that is relative to the package in which
* the specified class is defined. A path starting with a slash is treated
* as an absolute classpath location, for example:
* &quot;/org/springframework/whatever/foo.xml&quot;. A path which
* references a URL (e.g., a path prefixed with
* {@link ResourceUtils#CLASSPATH_URL_PREFIX classpath:},
* {@link ResourceUtils#FILE_URL_PREFIX file:}, {@code http:},
* etc.) will be added to the results unchanged.
* <p>The default implementation simply delegates to
* {@link TestContextResourceUtils#convertToClasspathResourcePaths}.
* <p>Subclasses can override this method to implement a different
* <em>location modification</em> strategy.
* @param clazz the class with which the locations are associated
@@ -238,21 +226,7 @@ public abstract class AbstractContextLoader implements SmartContextLoader {
* @since 2.5
*/
protected String[] modifyLocations(Class<?> clazz, String... locations) {
String[] modifiedLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
String path = locations[i];
if (path.startsWith(SLASH)) {
modifiedLocations[i] = ResourceUtils.CLASSPATH_URL_PREFIX + path;
}
else if (!ResourcePatternUtils.isUrl(path)) {
modifiedLocations[i] = ResourceUtils.CLASSPATH_URL_PREFIX + SLASH +
StringUtils.cleanPath(ClassUtils.classPackageAsResourcePath(clazz) + SLASH + path);
}
else {
modifiedLocations[i] = StringUtils.cleanPath(path);
}
}
return modifiedLocations;
return TestContextResourceUtils.convertToClasspathResourcePaths(clazz, locations);
}
/**

View File

@@ -38,6 +38,7 @@ import org.springframework.test.context.TestExecutionListener;
* <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.DatabaseInitializerTestExecutionListener}
* </ol>
* <li>Uses {@link DelegatingSmartContextLoader} as the default {@link ContextLoader}.
* <li>Builds a standard {@link MergedContextConfiguration}.
@@ -51,7 +52,8 @@ public class DefaultTestContextBootstrapper extends AbstractTestContextBootstrap
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.transaction.TransactionalTestExecutionListener",
"org.springframework.test.context.jdbc.DatabaseInitializerTestExecutionListener"));
/**
@@ -61,6 +63,7 @@ public class DefaultTestContextBootstrapper extends AbstractTestContextBootstrap
* <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.DatabaseInitializerTestExecutionListener}
* </ol>
*/
protected List<String> getDefaultTestExecutionListenerClassNames() {

View File

@@ -25,6 +25,7 @@ import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.jdbc.DatabaseInitializerTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.jdbc.JdbcTestUtils;
import org.springframework.transaction.PlatformTransactionManager;
@@ -54,6 +55,7 @@ import org.springframework.transaction.annotation.Transactional;
* @see org.springframework.test.context.ContextConfiguration
* @see org.springframework.test.context.TestExecutionListeners
* @see org.springframework.test.context.transaction.TransactionalTestExecutionListener
* @see org.springframework.test.context.jdbc.DatabaseInitializerTestExecutionListener
* @see org.springframework.test.context.transaction.TransactionConfiguration
* @see org.springframework.transaction.annotation.Transactional
* @see org.springframework.test.annotation.Rollback
@@ -62,7 +64,7 @@ import org.springframework.transaction.annotation.Transactional;
* @see org.springframework.test.jdbc.JdbcTestUtils
* @see org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests
*/
@TestExecutionListeners(TransactionalTestExecutionListener.class)
@TestExecutionListeners({ TransactionalTestExecutionListener.class, DatabaseInitializerTestExecutionListener.class })
@Transactional
public abstract class AbstractTransactionalTestNGSpringContextTests extends AbstractTestNGSpringContextTests {

View File

@@ -0,0 +1,216 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.context.transaction;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.test.context.TestContext;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;
import org.springframework.transaction.interceptor.DelegatingTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Utility methods for working with transactions and data access related beans
* within the <em>Spring TestContext Framework</em>. Mainly for internal use
* within the framework.
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 4.1
*/
public abstract class TestContextTransactionUtils {
private static final Log logger = LogFactory.getLog(TestContextTransactionUtils.class);
/**
* Default bean name for a {@link DataSource}: {@code "dataSource"}.
*/
public static final String DEFAULT_DATA_SOURCE_NAME = "dataSource";
/**
* Default bean name for a {@link PlatformTransactionManager}:
* {@code "transactionManager"}.
*/
public static final String DEFAULT_TRANSACTION_MANAGER_NAME = "transactionManager";
private TestContextTransactionUtils() {
/* prevent instantiation */
}
/**
* Retrieve the {@link DataSource} to use for the supplied {@linkplain TestContext
* test context}.
* <p>The following algorithm is used to retrieve the {@code DataSource}
* from the {@link org.springframework.context.ApplicationContext ApplicationContext}
* of the supplied test context:
* <ol>
* <li>Look up the {@code DataSource} by type and name, if the supplied
* {@code name} is non-empty.
* <li>Look up the {@code DataSource} by type.
* <li>Look up the {@code DataSource} by type and the
* {@linkplain #DEFAULT_DATA_SOURCE_NAME default data source name}.
* @param testContext the test context for which the {@code DataSource}
* should be retrieved; never {@code null}
* @param name the name of the {@code DataSource} to retrieve; may be {@code null}
* or <em>empty</em>
* @return the {@code DataSource} to use, or {@code null} if not found
* @throws BeansException if an error occurs while retrieving the {@code DataSource}
*/
public static DataSource retrieveDataSource(TestContext testContext, String name) {
Assert.notNull(testContext, "TestContext must not be null");
BeanFactory bf = testContext.getApplicationContext().getAutowireCapableBeanFactory();
try {
// look up by type and explicit name
if (StringUtils.hasText(name)) {
return bf.getBean(name, DataSource.class);
}
if (bf instanceof ListableBeanFactory) {
ListableBeanFactory lbf = (ListableBeanFactory) bf;
// look up single bean by type
Map<String, DataSource> dataSources = BeanFactoryUtils.beansOfTypeIncludingAncestors(lbf,
DataSource.class);
if (dataSources.size() == 1) {
return dataSources.values().iterator().next();
}
}
// look up by type and default name
return bf.getBean(DEFAULT_DATA_SOURCE_NAME, DataSource.class);
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Caught exception while retrieving DataSource for test context " + testContext, ex);
}
throw ex;
}
}
/**
* Retrieve the {@linkplain PlatformTransactionManager transaction manager}
* to use for the supplied {@linkplain TestContext test context}.
* <p>The following algorithm is used to retrieve the transaction manager
* from the {@link org.springframework.context.ApplicationContext ApplicationContext}
* of the supplied test context:
* <ol>
* <li>Look up the transaction manager by type and name, if the supplied
* {@code name} is non-empty.
* <li>Look up the transaction manager by type.
* <li>Look up the transaction manager via a {@link TransactionManagementConfigurer},
* if present.
* <li>Look up the transaction manager by type and the
* {@linkplain #DEFAULT_TRANSACTION_MANAGER_NAME default transaction manager
* name}.
* @param testContext the test context for which the transaction manager
* should be retrieved; never {@code null}
* @param name the name of the transaction manager to retrieve; may be
* {@code null} or <em>empty</em>
* @return the transaction manager to use, or {@code null} if not found
* @throws BeansException if an error occurs while retrieving the transaction manager
*/
public static PlatformTransactionManager retrieveTransactionManager(TestContext testContext, String name) {
Assert.notNull(testContext, "TestContext must not be null");
BeanFactory bf = testContext.getApplicationContext().getAutowireCapableBeanFactory();
try {
// look up by type and explicit name
if (StringUtils.hasText(name)) {
return bf.getBean(name, PlatformTransactionManager.class);
}
if (bf instanceof ListableBeanFactory) {
ListableBeanFactory lbf = (ListableBeanFactory) bf;
// look up single bean by type
Map<String, PlatformTransactionManager> txMgrs = BeanFactoryUtils.beansOfTypeIncludingAncestors(lbf,
PlatformTransactionManager.class);
if (txMgrs.size() == 1) {
return txMgrs.values().iterator().next();
}
// look up single TransactionManagementConfigurer
Map<String, TransactionManagementConfigurer> configurers = BeanFactoryUtils.beansOfTypeIncludingAncestors(
lbf, TransactionManagementConfigurer.class);
if (configurers.size() > 1) {
throw new IllegalStateException(
"Only one TransactionManagementConfigurer may exist in the ApplicationContext");
}
if (configurers.size() == 1) {
return configurers.values().iterator().next().annotationDrivenTransactionManager();
}
}
// look up by type and default name
return bf.getBean(DEFAULT_TRANSACTION_MANAGER_NAME, PlatformTransactionManager.class);
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Caught exception while retrieving transaction manager for test context " + testContext, ex);
}
throw ex;
}
}
/**
* Create a delegating {@link TransactionAttribute} for the supplied target
* {@link TransactionAttribute} and {@link TestContext}, using the names of
* the test class and test method as the name of the transaction.
*
* @param testContext the {@code TestContext} upon which to base the name; never {@code null}
* @param targetAttribute the {@code TransactionAttribute} to delegate to; never {@code null}
* @return the delegating {@code TransactionAttribute}
*/
public static TransactionAttribute createDelegatingTransactionAttribute(TestContext testContext,
TransactionAttribute targetAttribute) {
Assert.notNull(testContext, "TestContext must not be null");
Assert.notNull(targetAttribute, "Target TransactionAttribute must not be null");
return new TestContextTransactionAttribute(targetAttribute, testContext);
}
@SuppressWarnings("serial")
private static class TestContextTransactionAttribute extends DelegatingTransactionAttribute {
private final String name;
public TestContextTransactionAttribute(TransactionAttribute targetAttribute, TestContext testContext) {
super(targetAttribute);
this.name = testContext.getTestClass().getName() + "." + testContext.getTestMethod().getName();
}
@Override
public String getName() {
return this.name;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,17 +45,22 @@ public @interface TransactionConfiguration {
* The bean name of the {@link org.springframework.transaction.PlatformTransactionManager
* PlatformTransactionManager} that should be used to drive transactions.
*
* <p>This attribute is not required and only needs to be declared if there
* are multiple beans of type {@code PlatformTransactionManager} in the test's
* {@code ApplicationContext} <em>and</em> if one of the following is true.
* <ul>
* <li>the bean name of the desired {@code PlatformTransactionManager} is not
* "transactionManager"</li>
* <p>The name is only used if there is more than one bean of type
* {@code PlatformTransactionManager} in the test's {@code ApplicationContext}.
* If there is only one such bean, it is not necessary to specify a bean name.
*
* <p>Defaults to an empty string, requiring that one of the following is
* true:
* <ol>
* <li>There is only one bean of type {@code PlatformTransactionManager} in
* the test's {@code ApplicationContext}.</li>
* <li>{@link org.springframework.transaction.annotation.TransactionManagementConfigurer
* TransactionManagementConfigurer} was not implemented to specify which
* TransactionManagementConfigurer} has been implemented to specify which
* {@code PlatformTransactionManager} bean should be used for annotation-driven
* transaction management
* </ul>
* transaction management.</li>
* <li>The {@code PlatformTransactionManager} to use is named
* {@code "transactionManager"}.</li>
* </ol>
*
* <p><b>NOTE:</b> The XML {@code <tx:annotation-driven>} element also refers
* to a bean named "transactionManager" by default. If you are using both
@@ -63,7 +68,7 @@ public @interface TransactionConfiguration {
* bean - here in {@code @TransactionConfiguration} and also in
* {@code <tx:annotation-driven transaction-manager="...">}.
*/
String transactionManager() default "transactionManager";
String transactionManager() default "";
/**
* Should transactions be rolled back by default?

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,8 +29,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotatedElementUtils;
@@ -44,7 +42,6 @@ import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;
import org.springframework.transaction.interceptor.DelegatingTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttributeSource;
import org.springframework.util.Assert;
@@ -126,10 +123,10 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
* @see org.springframework.transaction.annotation.Transactional
* @see #getTransactionManager(TestContext, String)
*/
@SuppressWarnings("serial")
@Override
public void beforeTestMethod(TestContext testContext) throws Exception {
public void beforeTestMethod(final TestContext testContext) throws Exception {
final Method testMethod = testContext.getTestMethod();
final Class<?> testClass = testContext.getTestClass();
Assert.notNull(testMethod, "The test method of the supplied TestContext must not be null");
if (this.transactionContextCache.remove(testMethod) != null) {
@@ -138,17 +135,11 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
}
PlatformTransactionManager tm = null;
TransactionAttribute transactionAttribute = this.attributeSource.getTransactionAttribute(testMethod,
testContext.getTestClass());
TransactionAttribute transactionAttribute = this.attributeSource.getTransactionAttribute(testMethod, testClass);
if (transactionAttribute != null) {
transactionAttribute = new DelegatingTransactionAttribute(transactionAttribute) {
@Override
public String getName() {
return testMethod.getName();
}
};
transactionAttribute = TestContextTransactionUtils.createDelegatingTransactionAttribute(testContext,
transactionAttribute);
if (logger.isDebugEnabled()) {
logger.debug("Explicit transaction definition [" + transactionAttribute + "] found for test context "
@@ -302,7 +293,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
/**
* Get the {@link PlatformTransactionManager transaction manager} to use
* for the supplied {@link TestContext test context} and {@code qualifier}.
* for the supplied {@linkplain TestContext test context} and {@code qualifier}.
* <p>Delegates to {@link #getTransactionManager(TestContext)} if the
* supplied {@code qualifier} is {@code null} or empty.
* @param testContext the test context for which the transaction manager
@@ -340,6 +331,8 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
/**
* Get the {@link PlatformTransactionManager transaction manager} to use
* for the supplied {@link TestContext test context}.
* <p>The default implementation simply delegates to
* {@link TestContextTransactionUtils#retrieveTransactionManager}.
* @param testContext the test context for which the transaction manager
* should be retrieved
* @return the transaction manager to use, or {@code null} if not found
@@ -347,47 +340,8 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
* @see #getTransactionManager(TestContext, String)
*/
protected PlatformTransactionManager getTransactionManager(TestContext testContext) {
BeanFactory bf = testContext.getApplicationContext().getAutowireCapableBeanFactory();
String tmName = retrieveConfigurationAttributes(testContext).getTransactionManagerName();
try {
// look up by type and explicit name from @TransactionConfiguration
if (StringUtils.hasText(tmName) && !DEFAULT_TRANSACTION_MANAGER_NAME.equals(tmName)) {
return bf.getBean(tmName, PlatformTransactionManager.class);
}
if (bf instanceof ListableBeanFactory) {
ListableBeanFactory lbf = (ListableBeanFactory) bf;
// look up single bean by type
Map<String, PlatformTransactionManager> txMgrs = BeanFactoryUtils.beansOfTypeIncludingAncestors(lbf,
PlatformTransactionManager.class);
if (txMgrs.size() == 1) {
return txMgrs.values().iterator().next();
}
// look up single TransactionManagementConfigurer
Map<String, TransactionManagementConfigurer> configurers = BeanFactoryUtils.beansOfTypeIncludingAncestors(
lbf, TransactionManagementConfigurer.class);
if (configurers.size() > 1) {
throw new IllegalStateException(
"Only one TransactionManagementConfigurer may exist in the ApplicationContext");
}
if (configurers.size() == 1) {
return configurers.values().iterator().next().annotationDrivenTransactionManager();
}
}
// look up by type and default name from @TransactionConfiguration
return bf.getBean(DEFAULT_TRANSACTION_MANAGER_NAME, PlatformTransactionManager.class);
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Caught exception while retrieving transaction manager for test context " + testContext, ex);
}
throw ex;
}
return TestContextTransactionUtils.retrieveTransactionManager(testContext, tmName);
}
/**

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.context.util;
import java.util.ArrayList;
import java.util.List;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
/**
* Utility methods for working with resources within the <em>Spring TestContext
* Framework</em>. Mainly for internal use within the framework.
*
* @author Sam Brannen
* @author Tadaya Tsuyukubo
* @since 4.1
* @see org.springframework.util.ResourceUtils
* @see org.springframework.core.io.Resource
* @see org.springframework.core.io.ClassPathResource
* @see org.springframework.core.io.FileSystemResource
* @see org.springframework.core.io.UrlResource
* @see org.springframework.core.io.ResourceLoader
*/
public abstract class TestContextResourceUtils {
private static final String SLASH = "/";
private TestContextResourceUtils() {
/* prevent instantiation */
}
/**
* Convert the supplied paths to classpath resource paths.
*
* <p>For each of the supplied paths:
* <ul>
* <li>A plain path &mdash; for example, {@code "context.xml"} &mdash; will
* be treated as a classpath resource that is relative to the package in
* which the specified class is defined.
* <li>A path starting with a slash will be treated as an absolute path
* within the classpath, for example: {@code "/org/example/schema.sql"}.
* <li>A path which is prefixed with a URL protocol (e.g.,
* {@link ResourceUtils#CLASSPATH_URL_PREFIX classpath:},
* {@link ResourceUtils#FILE_URL_PREFIX file:}, {@code http:}, etc.) will be
* {@link StringUtils#cleanPath cleaned} but otherwise unmodified.
*
* @param clazz the class with which the paths are associated
* @param paths the paths to be converted
* @return a new array of converted resource paths
* @see #convertToResources
*/
public static String[] convertToClasspathResourcePaths(Class<?> clazz, String... paths) {
String[] convertedPaths = new String[paths.length];
for (int i = 0; i < paths.length; i++) {
String path = paths[i];
if (path.startsWith(SLASH)) {
convertedPaths[i] = ResourceUtils.CLASSPATH_URL_PREFIX + path;
}
else if (!ResourcePatternUtils.isUrl(path)) {
convertedPaths[i] = ResourceUtils.CLASSPATH_URL_PREFIX + SLASH
+ StringUtils.cleanPath(ClassUtils.classPackageAsResourcePath(clazz) + SLASH + path);
}
else {
convertedPaths[i] = StringUtils.cleanPath(path);
}
}
return convertedPaths;
}
/**
* Convert the supplied paths to {@link Resource} handles using the given
* {@link ResourceLoader}.
*
* @param resourceLoader the {@code ResourceLoader} to use to convert the paths
* @param paths the paths to be converted
* @return a new array of resources
* @see #convertToClasspathResourcePaths
*/
public static Resource[] convertToResources(ResourceLoader resourceLoader, String... paths) {
List<Resource> list = new ArrayList<Resource>();
for (String path : paths) {
list.add(resourceLoader.getResource(path));
}
return list.toArray(new Resource[list.size()]);
}
}

View File

@@ -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.", 3,
assertEquals("Num registered TELs for DefaultListenersExampleTestCase.", 4,
testContextManager.getTestExecutionListeners().size());
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.context.jdbc;
import org.junit.Test;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import static org.junit.Assert.*;
/**
* Integration tests that verify support for custom SQL script syntax
* configured via {@link DatabaseInitializer @DatabaseInitializer}.
*
* @author Sam Brannen
* @since 4.1
*/
@ContextConfiguration(classes = EmptyDatabaseConfig.class)
@DirtiesContext
public class CustomScriptSyntaxDatabaseInitializerTests extends AbstractTransactionalJUnit4SpringContextTests {
@Test
@DatabaseInitializers({//
@DatabaseInitializer("schema.sql"),//
@DatabaseInitializer(//
scripts = "data-add-users-with-custom-script-syntax.sql",//
commentPrefix = "`",//
blockCommentStartDelimiter = "#$",//
blockCommentEndDelimiter = "$#",//
separator = "@@"//
) //
})
public void methodLevelScripts() {
assertNumUsers(3);
}
protected void assertNumUsers(int expected) {
assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user"));
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.context.jdbc;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.test.context.TestContext;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
* Unit tests for {@link DatabaseInitializerTestExecutionListener}.
*
* @author Sam Brannen
* @since 4.1
*/
public class DatabaseInitializerTestExecutionListenerTests {
private final DatabaseInitializerTestExecutionListener listener = new DatabaseInitializerTestExecutionListener();
private final TestContext testContext = mock(TestContext.class);
@Test
public void missingValueAndScriptsAtClassLevel() throws Exception {
Class<?> clazz = MissingValueAndScriptsAtClassLevel.class;
Mockito.<Class<?>> when(testContext.getTestClass()).thenReturn(clazz);
when(testContext.getTestMethod()).thenReturn(clazz.getDeclaredMethod("foo"));
assertExceptionContains(clazz.getSimpleName() + ".sql");
}
@Test
public void missingValueAndScriptsAtMethodLevel() throws Exception {
Class<?> clazz = MissingValueAndScriptsAtMethodLevel.class;
Mockito.<Class<?>> when(testContext.getTestClass()).thenReturn(clazz);
when(testContext.getTestMethod()).thenReturn(clazz.getDeclaredMethod("foo"));
assertExceptionContains(clazz.getSimpleName() + ".foo" + ".sql");
}
@Test
public void valueAndScriptsDeclared() throws Exception {
Class<?> clazz = ValueAndScriptsDeclared.class;
Mockito.<Class<?>> when(testContext.getTestClass()).thenReturn(clazz);
when(testContext.getTestMethod()).thenReturn(clazz.getDeclaredMethod("valueAndScriptsDeclared"));
assertExceptionContains("Only one declaration of SQL script paths is permitted");
}
private void assertExceptionContains(String msg) throws Exception {
try {
listener.beforeTestMethod(testContext);
fail("Should have thrown an IllegalStateException.");
}
catch (IllegalStateException e) {
assertTrue("Exception message should contain: " + msg, e.getMessage().contains(msg));
}
}
// -------------------------------------------------------------------------
@DatabaseInitializer
static class MissingValueAndScriptsAtClassLevel {
public void foo() {
}
}
static class MissingValueAndScriptsAtMethodLevel {
@DatabaseInitializer
public void foo() {
}
}
static class ValueAndScriptsDeclared {
@DatabaseInitializer(value = "foo", scripts = "bar")
public void valueAndScriptsDeclared() {
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.context.jdbc;
import org.junit.Test;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import static org.junit.Assert.*;
/**
* Integration tests that verify support for default SQL script detection.
*
* @author Sam Brannen
* @since 4.1
*/
@ContextConfiguration(classes = EmptyDatabaseConfig.class)
@DatabaseInitializer
@DirtiesContext
public class DefaultScriptDetectionDatabaseInitializerTests extends AbstractTransactionalJUnit4SpringContextTests {
@Test
public void classLevel() {
assertNumUsers(2);
}
@Test
@DatabaseInitializer
public void methodLevel() {
assertNumUsers(3);
}
protected void assertNumUsers(int expected) {
assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user"));
}
}

View File

@@ -0,0 +1,10 @@
DROP TABLE user IF EXISTS;
CREATE TABLE user (
name VARCHAR(20) NOT NULL,
PRIMARY KEY(name)
);
INSERT INTO user VALUES('Dilbert');
INSERT INTO user VALUES('Dogbert');
INSERT INTO user VALUES('Catbert');

View File

@@ -0,0 +1,9 @@
DROP TABLE user IF EXISTS;
CREATE TABLE user (
name VARCHAR(20) NOT NULL,
PRIMARY KEY(name)
);
INSERT INTO user VALUES('Dilbert');
INSERT INTO user VALUES('Dogbert');

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.context.jdbc;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.transaction.PlatformTransactionManager;
/**
* Empty database configuration class for SQL script integration tests.
*
* @author Sam Brannen
* @since 4.1
*/
@Configuration
public class EmptyDatabaseConfig {
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder().build();
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.context.jdbc;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.junit.Test;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
import static org.junit.Assert.*;
/**
* Integration tests that verify support for using
* {@link DatabaseInitializer @DatabaseInitializer} and
* {@link DatabaseInitializers @DatabaseInitializers} as a meta-annotations.
*
* @author Sam Brannen
* @since 4.1
*/
@ContextConfiguration(classes = EmptyDatabaseConfig.class)
@DirtiesContext
public class MetaAnnotationDatabaseInitializerTests extends AbstractTransactionalJUnit4SpringContextTests {
@Test
@MetaDbInitializer
public void metaDatabaseInitializer() {
assertNumUsers(1);
}
@Test
@MetaDbInitializers
public void metaDatabaseInitializers() {
assertNumUsers(1);
}
protected void assertNumUsers(int expected) {
assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user"));
}
@DatabaseInitializer({ "drop-schema.sql", "schema.sql", "data.sql" })
@Retention(RUNTIME)
@Target(METHOD)
static @interface MetaDbInitializer {
}
@DatabaseInitializers({//
@DatabaseInitializer("drop-schema.sql"),//
@DatabaseInitializer("schema.sql"),//
@DatabaseInitializer("data.sql") //
})
@Retention(RUNTIME)
@Target(METHOD)
static @interface MetaDbInitializers {
}
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.context.jdbc;
import javax.sql.DataSource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.PlatformTransactionManager;
import static org.junit.Assert.*;
/**
* Integration tests for {@link DatabaseInitializer @DatabaseInitializer} that
* verify support for multiple {@link DataSource}s and {@link PlatformTransactionManager}s.
*
* @author Sam Brannen
* @since 4.1
* @see MultipleDataSourcesAndTransactionManagersTransactionalDatabaseInitializerTests
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@DirtiesContext
public class MultipleDataSourcesAndTransactionManagersDatabaseInitializerTests {
@Autowired
private DataSource dataSource1;
@Autowired
private DataSource dataSource2;
@Test
@DatabaseInitializer(scripts = "data-add-dogbert.sql", dataSource = "dataSource1", transactionManager = "txMgr1")
public void database1() {
assertUsersExist(new JdbcTemplate(dataSource1), "Dilbert", "Dogbert");
}
@Test
@DatabaseInitializer(scripts = "data-add-catbert.sql", dataSource = "dataSource2", transactionManager = "txMgr2")
public void database2() {
assertUsersExist(new JdbcTemplate(dataSource2), "Dilbert", "Catbert");
}
private void assertUsersExist(JdbcTemplate jdbcTemplate, String... users) {
String query = "select count(name) from user where name =?";
for (String user : users) {
assertTrue("User [" + user + "] must exist.",
jdbcTemplate.queryForObject(query, Integer.class, user).intValue() == 1);
}
}
@Configuration
static class Config {
@Bean
public PlatformTransactionManager txMgr1() {
return new DataSourceTransactionManager(dataSource1());
}
@Bean
public PlatformTransactionManager txMgr2() {
return new DataSourceTransactionManager(dataSource2());
}
@Bean
public DataSource dataSource1() {
return new EmbeddedDatabaseBuilder()//
.setName("database1")//
.addScript("classpath:/org/springframework/test/context/jdbc/schema.sql")//
.addScript("classpath:/org/springframework/test/context/jdbc/data.sql")//
.build();
}
@Bean
public DataSource dataSource2() {
return new EmbeddedDatabaseBuilder()//
.setName("database2")//
.addScript("classpath:/org/springframework/test/context/jdbc/schema.sql")//
.addScript("classpath:/org/springframework/test/context/jdbc/data.sql")//
.build();
}
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.context.jdbc;
import javax.sql.DataSource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.Assert.*;
/**
* Exact copy of {@link MultipleDataSourcesAndTransactionManagersDatabaseInitializerTests},
* except that the test methods are transactional.
*
* @author Sam Brannen
* @since 4.1
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@DirtiesContext
public class MultipleDataSourcesAndTransactionManagersTransactionalDatabaseInitializerTests {
@Autowired
private DataSource dataSource1;
@Autowired
private DataSource dataSource2;
@Test
@Transactional("txMgr1")
@DatabaseInitializer(scripts = "data-add-dogbert.sql", dataSource = "dataSource1", transactionManager = "txMgr1")
public void database1() {
assertUsersExist(new JdbcTemplate(dataSource1), "Dilbert", "Dogbert");
}
@Test
@Transactional("txMgr2")
@DatabaseInitializer(scripts = "data-add-catbert.sql", dataSource = "dataSource2", transactionManager = "txMgr2")
public void database2() {
assertUsersExist(new JdbcTemplate(dataSource2), "Dilbert", "Catbert");
}
private void assertUsersExist(JdbcTemplate jdbcTemplate, String... users) {
String query = "select count(name) from user where name =?";
for (String user : users) {
assertTrue("User [" + user + "] must exist.",
jdbcTemplate.queryForObject(query, Integer.class, user).intValue() == 1);
}
}
@Configuration
static class Config {
@Bean
public PlatformTransactionManager txMgr1() {
return new DataSourceTransactionManager(dataSource1());
}
@Bean
public PlatformTransactionManager txMgr2() {
return new DataSourceTransactionManager(dataSource2());
}
@Bean
public DataSource dataSource1() {
return new EmbeddedDatabaseBuilder()//
.setName("database1")//
.addScript("classpath:/org/springframework/test/context/jdbc/schema.sql")//
.addScript("classpath:/org/springframework/test/context/jdbc/data.sql")//
.build();
}
@Bean
public DataSource dataSource2() {
return new EmbeddedDatabaseBuilder()//
.setName("database2")//
.addScript("classpath:/org/springframework/test/context/jdbc/schema.sql")//
.addScript("classpath:/org/springframework/test/context/jdbc/data.sql")//
.build();
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.context.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.SpringJUnit4ClassRunner;
import org.springframework.test.jdbc.JdbcTestUtils;
import static org.junit.Assert.*;
/**
* Integration tests which verify that scripts executed via
* {@link DatabaseInitializer @DatabaseInitializer} will persist between
* non-transactional test methods.
*
* @author Sam Brannen
* @since 4.1
*/
@RunWith(SpringJUnit4ClassRunner.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@ContextConfiguration(classes = EmptyDatabaseConfig.class)
@DatabaseInitializer({ "schema.sql", "data.sql" })
@DirtiesContext
public class NonTransactionalDatabaseInitializerTests {
protected JdbcTemplate jdbcTemplate;
@Autowired
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Test
// test##_ prefix is required for @FixMethodOrder.
public void test01_classLevelScripts() {
assertNumUsers(1);
}
@Test
@DatabaseInitializers(@DatabaseInitializer("data-add-dogbert.sql"))
// test##_ prefix is required for @FixMethodOrder.
public void test02_methodLevelScripts() {
assertNumUsers(2);
}
protected void assertNumUsers(int expected) {
assertEquals("Number of rows in the 'user' table.", expected,
JdbcTestUtils.countRowsInTable(jdbcTemplate, "user"));
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.context.jdbc;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.transaction.PlatformTransactionManager;
/**
* Database configuration class for SQL script integration tests with the 'user'
* table already created.
*
* @author Sam Brannen
* @since 4.1
*/
@Configuration
public class PopulatedSchemaDatabaseConfig {
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()//
.addScript("classpath:/org/springframework/test/context/jdbc/schema.sql") //
.build();
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.context.jdbc;
import org.junit.Test;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import org.springframework.test.context.transaction.AfterTransaction;
import org.springframework.test.context.transaction.BeforeTransaction;
import static org.junit.Assert.*;
/**
* Transactional integration tests that verify rollback semantics for
* {@link DatabaseInitializer @DatabaseInitializer} support.
*
* @author Sam Brannen
* @since 4.1
*/
@ContextConfiguration(classes = PopulatedSchemaDatabaseConfig.class)
@DirtiesContext
public class PopulatedSchemaTransactionalDatabaseInitializerTests extends AbstractTransactionalJUnit4SpringContextTests {
@BeforeTransaction
@AfterTransaction
public void verifyPreAndPostTransactionDatabaseState() {
assertNumUsers(0);
}
@Test
@DatabaseInitializers(@DatabaseInitializer("data-add-dogbert.sql"))
public void methodLevelScripts() {
assertNumUsers(1);
}
protected void assertNumUsers(int expected) {
assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user"));
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.context.jdbc;
import org.junit.Test;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import org.springframework.test.context.transaction.AfterTransaction;
import org.springframework.test.context.transaction.BeforeTransaction;
import static org.junit.Assert.*;
/**
* Transactional integration tests that verify commit semantics for
* {@link DatabaseInitializer#requireNewTransaction}.
*
* @author Sam Brannen
* @since 4.1
*/
@ContextConfiguration(classes = PopulatedSchemaDatabaseConfig.class)
@DirtiesContext
public class RequiresNewTransactionDatabaseInitializerTests extends AbstractTransactionalJUnit4SpringContextTests {
@BeforeTransaction
public void beforeTransaction() {
assertNumUsers(0);
}
@Test
@DatabaseInitializers(@DatabaseInitializer(scripts = "data-add-dogbert.sql", requireNewTransaction = true))
public void methodLevelScripts() {
assertNumUsers(1);
}
@AfterTransaction
public void afterTransaction() {
assertNumUsers(1);
}
protected void assertNumUsers(int expected) {
assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user"));
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.context.jdbc;
import org.junit.FixMethodOrder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runners.MethodSorters;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.jdbc.DatabaseInitializer.ExecutionPhase;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import org.springframework.test.context.transaction.AfterTransaction;
import static org.junit.Assert.*;
/**
* Transactional integration tests for {@link DatabaseInitializer @DatabaseInitializer}
* that verify proper support for {@link ExecutionPhase#AFTER_TEST_METHOD}.
*
* @author Sam Brannen
* @since 4.1
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@ContextConfiguration(classes = EmptyDatabaseConfig.class)
@DirtiesContext
public class TransactionalAfterTestMethodDatabaseInitializerTests extends AbstractTransactionalJUnit4SpringContextTests {
@Rule
public TestName testName = new TestName();
@AfterTransaction
public void afterTransaction() {
if ("test01".equals(testName.getMethodName())) {
try {
assertNumUsers(99);
fail("Should throw a BadSqlGrammarException after test01, assuming 'drop-schema.sql' was executed");
}
catch (BadSqlGrammarException e) {
/* expected */
}
}
}
@Test
@DatabaseInitializers({//
@DatabaseInitializer({ "schema.sql", "data.sql" }),//
@DatabaseInitializer(scripts = "drop-schema.sql", executionPhase = ExecutionPhase.AFTER_TEST_METHOD) //
})
// test## is required for @FixMethodOrder.
public void test01() {
assertNumUsers(1);
}
@Test
@DatabaseInitializers(@DatabaseInitializer({ "schema.sql", "data.sql", "data-add-dogbert.sql" }))
// test## is required for @FixMethodOrder.
public void test02() {
assertNumUsers(2);
}
protected void assertNumUsers(int expected) {
assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user"));
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.context.jdbc;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import static org.junit.Assert.*;
/**
* Transactional integration tests for {@link DatabaseInitializer @DatabaseInitializer}
* support.
*
* @author Sam Brannen
* @since 4.1
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@ContextConfiguration(classes = EmptyDatabaseConfig.class)
@DatabaseInitializers(@DatabaseInitializer({ "schema.sql", "data.sql" }))
@DirtiesContext
public class TransactionalDatabaseInitializerTests extends AbstractTransactionalJUnit4SpringContextTests {
@Test
// test##_ prefix is required for @FixMethodOrder.
public void test01_classLevelScripts() {
assertNumUsers(1);
}
@Test
@DatabaseInitializers(@DatabaseInitializer({ "drop-schema.sql", "schema.sql", "data.sql", "data-add-dogbert.sql" }))
// test##_ prefix is required for @FixMethodOrder.
public void test02_methodLevelScripts() {
assertNumUsers(2);
}
protected void assertNumUsers(int expected) {
assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user"));
}
}

View File

@@ -0,0 +1 @@
INSERT INTO user VALUES('Catbert');

View File

@@ -0,0 +1 @@
INSERT INTO user VALUES('Dogbert');

View File

@@ -0,0 +1,22 @@
` custom single-line comment
#$ custom
block
comment
$#
INSERT
INTO
user
VALUES('Dilbert')
@@
` custom single-line comment
INSERT INTO user VALUES('Dogbert')@@
INSERT INTO user VALUES('Catbert')@@

View File

@@ -0,0 +1 @@
INSERT INTO user VALUES('Dilbert');

View File

@@ -0,0 +1 @@
DROP TABLE user IF EXISTS;

View File

@@ -0,0 +1,4 @@
CREATE TABLE user (
name VARCHAR(20) NOT NULL,
PRIMARY KEY(name)
);

View File

@@ -44,7 +44,7 @@ public class ContextLoaderUtilsMergedConfigTests extends AbstractContextLoaderUt
assertMergedConfig(
mergedConfig,
testClass,
new String[] { "classpath:/org/springframework/test/context/support/AbstractContextLoaderUtilsTests$BareAnnotations-context.xml" },
new String[] { "classpath:org/springframework/test/context/support/AbstractContextLoaderUtilsTests$BareAnnotations-context.xml" },
EMPTY_CLASS_ARRAY, DelegatingSmartContextLoader.class);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -96,7 +96,7 @@ public class GenericXmlContextLoaderResourceLocationsTests {
{
ClasspathExistentDefaultLocationsTestCase.class,
new String[] { "classpath:/org/springframework/test/context/support/GenericXmlContextLoaderResourceLocationsTests$1ClasspathExistentDefaultLocationsTestCase-context.xml" } },
new String[] { "classpath:org/springframework/test/context/support/GenericXmlContextLoaderResourceLocationsTests$1ClasspathExistentDefaultLocationsTestCase-context.xml" } },
{
ImplicitClasspathLocationsTestCase.class,

View File

@@ -192,13 +192,13 @@ public class TransactionalTestExecutionListenerTests {
@Test
public void retrieveConfigurationAttributesWithMissingTransactionConfiguration() throws Exception {
assertTransactionConfigurationAttributes(MissingTransactionConfigurationTestCase.class, "transactionManager",
assertTransactionConfigurationAttributes(MissingTransactionConfigurationTestCase.class, "",
true);
}
@Test
public void retrieveConfigurationAttributesWithEmptyTransactionConfiguration() throws Exception {
assertTransactionConfigurationAttributes(EmptyTransactionConfigurationTestCase.class, "transactionManager",
assertTransactionConfigurationAttributes(EmptyTransactionConfigurationTestCase.class, "",
true);
}