提交 5fd6ebb5 编写于 作者: S Sam Brannen

Introduce annotation to execute SQL scripts in the TCF

Prior to this commit, it was possible to execute SQL scripts
programmatically via ResourceDatabasePopulator, JdbcTestUtils, and
ScriptUtils. Furthermore, it was also possible to execute SQL scripts
declaratively via the <jdbc> XML namespace. However, it was not
possible to execute SQL scripts declaratively on a per test class or
per test method basis.

This commit makes it possible to declaratively configure SQL scripts
for execution in integration tests via annotations that can be declared
at the class or method level. Details follow.

 - Introduced a repeatable @DatabaseInitializer annotation that can be
   used to configure SQL scripts at the class or method level with
   method-level overrides. @DatabaseInitializers serves as a container
   for @DatabaseInitializer.

 - Introduced a new DatabaseInitializerTestExecutionListener that is
   responsible for parsing @DatabaseInitializer and
   @DatabaseInitializers and executing SQL scripts.

 - DatabaseInitializerTestExecutionListener is registered by default in
   abstract base test classes as well as in TestContextBootstrapper
   implementations.

 - @DatabaseInitializer and @DatabaseInitializers may be used as
   meta-annotations; however, attribute overrides are not currently
   supported for repeatable annotations used as meta-annotations. This
   is a known limitation of Spring's AnnotationUtils.

 - The semantics for locating SQL script resources is consistent with
   @ContextConfiguration's semantics for locating XML configuration
   files. In addition, a default SQL script can be detected based
   either on the name of the annotated class or on the name of the
   annotated test method.

 - @DatabaseInitializer allows for specifying which DataSource and
   PlatformTransactionManager to use from the test's
   ApplicationContext, including default conventions consistent with
   TransactionalTestExecutionListener and @TransactionConfiguration.

 - @DatabaseInitializer supports all of the script configuration options
   currently supported by ResourceDatabasePopulator.

 - @DatabaseInitializer and DatabaseInitializerTestExecutionListener
   support execution phases for scripts that dictate when SQL scripts
   are executed (i.e., before or after a test method).

 - SQL scripts can be executed within the current test's transaction if
   present, outside of the current test's transaction if present, or
   always in a new transaction, depending on the value of the boolean
   requireNewTransaction flag in @DatabaseInitializer.

 - DatabaseInitializerTestExecutionListener delegates to
   ResourceDatabasePopulator#execute to actually execute the scripts.

Issue: SPR-7655
上级 8fb0f5c7
......@@ -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;
}
/**
......
/*
* 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;
}
/*
* 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);
}
}
}
/*
* 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();
}
......@@ -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 {
......
/*
* 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);
}
/**
......
......@@ -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() {
......
......@@ -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 {
......
/*
* 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;
}
}
}
/*
* 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?
......
/*
* 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);
}
/**
......
/*
* 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()]);
}
}
......@@ -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());
}
......
/*
* 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"));
}
}
/*
* 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() {
}
}
}
/*
* 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"));
}
}
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');
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');
/*
* 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();
}
}
/*
* 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 {
}
}
/*
* 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();
}
}
}
/*
* 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();
}
}
}
/*
* 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"));
}
}
/*
* 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();
}
}
/*
* 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"));
}
}
/*
* 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"));
}
}
/*
* 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"));
}
}
/*
* 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"));
}
}
INSERT INTO user VALUES('Catbert');
\ No newline at end of file
INSERT INTO user VALUES('Dogbert');
\ No newline at end of file
` 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')@@
INSERT INTO user VALUES('Dilbert');
\ No newline at end of file
CREATE TABLE user (
name VARCHAR(20) NOT NULL,
PRIMARY KEY(name)
);
......@@ -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);
}
......
/*
* 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,
......
......@@ -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);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册