Merge class-level and method-level @Sql declarations
Prior to these commits method-level @Sql declarations always overrode class-level @Sql declarations, which required developers to redeclare class-level @Sql declarations on test methods (e.g., via copy-and-paste) in order to combine them with method-level @Sql declarations. These commits provide developers the ability to optionally merge class-level and method-level @Sql declarations via a new @SqlMergeMode annotation that can be applied at the class level or method level, with method-level declarations of @SqlMergeMode overriding class-level declarations. For example, @SqlMergeMode(MERGE) switches from the default OVERRIDE behavior to merging behavior, with class-level SQL scripts and statements executed before method-level SQL scripts and statements. Closes gh-1835
This commit is contained in:
commit
9cb15ba602
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -31,7 +31,8 @@ import org.springframework.core.annotation.AliasFor;
|
||||||
* SQL {@link #scripts} and {@link #statements} to be executed against a given
|
* SQL {@link #scripts} and {@link #statements} to be executed against a given
|
||||||
* database during integration tests.
|
* database during integration tests.
|
||||||
*
|
*
|
||||||
* <p>Method-level declarations override class-level declarations.
|
* <p>Method-level declarations override class-level declarations by default,
|
||||||
|
* but this behavior can be configured via {@link SqlMergeMode @SqlMergeMode}.
|
||||||
*
|
*
|
||||||
* <p>Script execution is performed by the {@link SqlScriptsTestExecutionListener},
|
* <p>Script execution is performed by the {@link SqlScriptsTestExecutionListener},
|
||||||
* which is enabled by default.
|
* which is enabled by default.
|
||||||
|
@ -55,6 +56,7 @@ import org.springframework.core.annotation.AliasFor;
|
||||||
* @author Sam Brannen
|
* @author Sam Brannen
|
||||||
* @since 4.1
|
* @since 4.1
|
||||||
* @see SqlConfig
|
* @see SqlConfig
|
||||||
|
* @see SqlMergeMode
|
||||||
* @see SqlGroup
|
* @see SqlGroup
|
||||||
* @see SqlScriptsTestExecutionListener
|
* @see SqlScriptsTestExecutionListener
|
||||||
* @see org.springframework.transaction.annotation.Transactional
|
* @see org.springframework.transaction.annotation.Transactional
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2019 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
|
||||||
|
*
|
||||||
|
* https://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.ElementType;
|
||||||
|
import java.lang.annotation.Inherited;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code @SqlMergeMode} is used to annotate a test class or test method to
|
||||||
|
* configure whether method-level {@code @Sql} declarations are merged with
|
||||||
|
* class-level {@code @Sql} declarations.
|
||||||
|
*
|
||||||
|
* <p>A method-level {@code @SqlMergeMode} declaration overrides a class-level
|
||||||
|
* declaration.
|
||||||
|
*
|
||||||
|
* <p>If {@code @SqlMergeMode} is not declared on a test class or test method,
|
||||||
|
* {@link MergeMode#OVERRIDE} will be used by default.
|
||||||
|
*
|
||||||
|
* <p>This annotation may be used as a <em>meta-annotation</em> to create custom
|
||||||
|
* <em>composed annotations</em> with attribute overrides.
|
||||||
|
*
|
||||||
|
* @author Sam Brannen
|
||||||
|
* @author Dmitry Semukhin
|
||||||
|
* @since 5.2
|
||||||
|
* @see Sql
|
||||||
|
* @see MergeMode#MERGE
|
||||||
|
* @see MergeMode#OVERRIDE
|
||||||
|
*/
|
||||||
|
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
@Inherited
|
||||||
|
public @interface SqlMergeMode {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether method-level {@code @Sql} annotations should be merged
|
||||||
|
* with class-level {@code @Sql} annotations or override them.
|
||||||
|
*/
|
||||||
|
MergeMode value();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration of <em>modes</em> that dictate whether method-level {@code @Sql}
|
||||||
|
* declarations are merged with class-level {@code @Sql} declarations.
|
||||||
|
*/
|
||||||
|
enum MergeMode {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that method-level {@code @Sql} declarations should be merged
|
||||||
|
* with class-level {@code @Sql} declarations, with class-level SQL
|
||||||
|
* scripts and statements executed before method-level scripts and
|
||||||
|
* statements.
|
||||||
|
*/
|
||||||
|
MERGE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that method-level {@code @Sql} declarations should override
|
||||||
|
* class-level {@code @Sql} declarations.
|
||||||
|
*/
|
||||||
|
OVERRIDE
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package org.springframework.test.context.jdbc;
|
package org.springframework.test.context.jdbc;
|
||||||
|
|
||||||
|
import java.lang.reflect.AnnotatedElement;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -30,11 +31,13 @@ import org.springframework.core.io.ByteArrayResource;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.test.context.TestContext;
|
import org.springframework.test.context.TestContext;
|
||||||
import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
|
import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
|
||||||
import org.springframework.test.context.jdbc.SqlConfig.ErrorMode;
|
import org.springframework.test.context.jdbc.SqlConfig.ErrorMode;
|
||||||
import org.springframework.test.context.jdbc.SqlConfig.TransactionMode;
|
import org.springframework.test.context.jdbc.SqlConfig.TransactionMode;
|
||||||
|
import org.springframework.test.context.jdbc.SqlMergeMode.MergeMode;
|
||||||
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
||||||
import org.springframework.test.context.transaction.TestContextTransactionUtils;
|
import org.springframework.test.context.transaction.TestContextTransactionUtils;
|
||||||
import org.springframework.test.context.util.TestContextResourceUtils;
|
import org.springframework.test.context.util.TestContextResourceUtils;
|
||||||
|
@ -81,6 +84,7 @@ import org.springframework.util.StringUtils;
|
||||||
* locate these beans.
|
* locate these beans.
|
||||||
*
|
*
|
||||||
* @author Sam Brannen
|
* @author Sam Brannen
|
||||||
|
* @author Dmitry Semukhin
|
||||||
* @since 4.1
|
* @since 4.1
|
||||||
* @see Sql
|
* @see Sql
|
||||||
* @see SqlConfig
|
* @see SqlConfig
|
||||||
|
@ -108,7 +112,7 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
|
||||||
* {@link TestContext} <em>before</em> the current test method.
|
* {@link TestContext} <em>before</em> the current test method.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void beforeTestMethod(TestContext testContext) throws Exception {
|
public void beforeTestMethod(TestContext testContext) {
|
||||||
executeSqlScripts(testContext, ExecutionPhase.BEFORE_TEST_METHOD);
|
executeSqlScripts(testContext, ExecutionPhase.BEFORE_TEST_METHOD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +121,7 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
|
||||||
* {@link TestContext} <em>after</em> the current test method.
|
* {@link TestContext} <em>after</em> the current test method.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void afterTestMethod(TestContext testContext) throws Exception {
|
public void afterTestMethod(TestContext testContext) {
|
||||||
executeSqlScripts(testContext, ExecutionPhase.AFTER_TEST_METHOD);
|
executeSqlScripts(testContext, ExecutionPhase.AFTER_TEST_METHOD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,22 +129,58 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
|
||||||
* Execute SQL scripts configured via {@link Sql @Sql} for the supplied
|
* Execute SQL scripts configured via {@link Sql @Sql} for the supplied
|
||||||
* {@link TestContext} and {@link ExecutionPhase}.
|
* {@link TestContext} and {@link ExecutionPhase}.
|
||||||
*/
|
*/
|
||||||
private void executeSqlScripts(TestContext testContext, ExecutionPhase executionPhase) throws Exception {
|
private void executeSqlScripts(TestContext testContext, ExecutionPhase executionPhase) {
|
||||||
boolean classLevel = false;
|
Method testMethod = testContext.getTestMethod();
|
||||||
|
Class<?> testClass = testContext.getTestClass();
|
||||||
|
|
||||||
Set<Sql> sqlAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(
|
if (mergeSqlAnnotations(testContext)) {
|
||||||
testContext.getTestMethod(), Sql.class, SqlGroup.class);
|
executeSqlScripts(getSqlAnnotationsFor(testClass), testContext, executionPhase, true);
|
||||||
if (sqlAnnotations.isEmpty()) {
|
executeSqlScripts(getSqlAnnotationsFor(testMethod), testContext, executionPhase, false);
|
||||||
sqlAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(
|
}
|
||||||
testContext.getTestClass(), Sql.class, SqlGroup.class);
|
else {
|
||||||
if (!sqlAnnotations.isEmpty()) {
|
Set<Sql> methodLevelSqlAnnotations = getSqlAnnotationsFor(testMethod);
|
||||||
classLevel = true;
|
if (!methodLevelSqlAnnotations.isEmpty()) {
|
||||||
|
executeSqlScripts(methodLevelSqlAnnotations, testContext, executionPhase, false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
executeSqlScripts(getSqlAnnotationsFor(testClass), testContext, executionPhase, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (Sql sql : sqlAnnotations) {
|
/**
|
||||||
executeSqlScripts(sql, executionPhase, testContext, classLevel);
|
* Determine if method-level {@code @Sql} annotations should be merged with
|
||||||
|
* class-level {@code @Sql} annotations.
|
||||||
|
*/
|
||||||
|
private boolean mergeSqlAnnotations(TestContext testContext) {
|
||||||
|
SqlMergeMode sqlMergeMode = getSqlMergeModeFor(testContext.getTestMethod());
|
||||||
|
if (sqlMergeMode == null) {
|
||||||
|
sqlMergeMode = getSqlMergeModeFor(testContext.getTestClass());
|
||||||
}
|
}
|
||||||
|
return (sqlMergeMode != null && sqlMergeMode.value() == MergeMode.MERGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@code @SqlMergeMode} annotation declared on the supplied {@code element}.
|
||||||
|
*/
|
||||||
|
private SqlMergeMode getSqlMergeModeFor(AnnotatedElement element) {
|
||||||
|
return AnnotatedElementUtils.findMergedAnnotation(element, SqlMergeMode.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@code @Sql} annotations declared on the supplied {@code element}.
|
||||||
|
*/
|
||||||
|
private Set<Sql> getSqlAnnotationsFor(AnnotatedElement element) {
|
||||||
|
return AnnotatedElementUtils.getMergedRepeatableAnnotations(element, Sql.class, SqlGroup.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute SQL scripts for the supplied {@link Sql @Sql} annotations.
|
||||||
|
*/
|
||||||
|
private void executeSqlScripts(
|
||||||
|
Set<Sql> sqlAnnotations, TestContext testContext, ExecutionPhase executionPhase, boolean classLevel) {
|
||||||
|
|
||||||
|
sqlAnnotations.forEach(sql -> executeSqlScripts(sql, executionPhase, testContext, classLevel));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -153,8 +193,8 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
|
||||||
* @param testContext the current {@code TestContext}
|
* @param testContext the current {@code TestContext}
|
||||||
* @param classLevel {@code true} if {@link Sql @Sql} was declared at the class level
|
* @param classLevel {@code true} if {@link Sql @Sql} was declared at the class level
|
||||||
*/
|
*/
|
||||||
private void executeSqlScripts(Sql sql, ExecutionPhase executionPhase, TestContext testContext, boolean classLevel)
|
private void executeSqlScripts(
|
||||||
throws Exception {
|
Sql sql, ExecutionPhase executionPhase, TestContext testContext, boolean classLevel) {
|
||||||
|
|
||||||
if (executionPhase != sql.executionPhase()) {
|
if (executionPhase != sql.executionPhase()) {
|
||||||
return;
|
return;
|
||||||
|
@ -166,15 +206,6 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
|
||||||
mergedSqlConfig, executionPhase, testContext));
|
mergedSqlConfig, executionPhase, testContext));
|
||||||
}
|
}
|
||||||
|
|
||||||
final ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
|
|
||||||
populator.setSqlScriptEncoding(mergedSqlConfig.getEncoding());
|
|
||||||
populator.setSeparator(mergedSqlConfig.getSeparator());
|
|
||||||
populator.setCommentPrefix(mergedSqlConfig.getCommentPrefix());
|
|
||||||
populator.setBlockCommentStartDelimiter(mergedSqlConfig.getBlockCommentStartDelimiter());
|
|
||||||
populator.setBlockCommentEndDelimiter(mergedSqlConfig.getBlockCommentEndDelimiter());
|
|
||||||
populator.setContinueOnError(mergedSqlConfig.getErrorMode() == ErrorMode.CONTINUE_ON_ERROR);
|
|
||||||
populator.setIgnoreFailedDrops(mergedSqlConfig.getErrorMode() == ErrorMode.IGNORE_FAILED_DROPS);
|
|
||||||
|
|
||||||
String[] scripts = getScripts(sql, testContext, classLevel);
|
String[] scripts = getScripts(sql, testContext, classLevel);
|
||||||
scripts = TestContextResourceUtils.convertToClasspathResourcePaths(testContext.getTestClass(), scripts);
|
scripts = TestContextResourceUtils.convertToClasspathResourcePaths(testContext.getTestClass(), scripts);
|
||||||
List<Resource> scriptResources = TestContextResourceUtils.convertToResourceList(
|
List<Resource> scriptResources = TestContextResourceUtils.convertToResourceList(
|
||||||
|
@ -185,6 +216,8 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
|
||||||
scriptResources.add(new ByteArrayResource(stmt.getBytes(), "from inlined SQL statement: " + stmt));
|
scriptResources.add(new ByteArrayResource(stmt.getBytes(), "from inlined SQL statement: " + stmt));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ResourceDatabasePopulator populator = createDatabasePopulator(mergedSqlConfig);
|
||||||
populator.setScripts(scriptResources.toArray(new Resource[0]));
|
populator.setScripts(scriptResources.toArray(new Resource[0]));
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("Executing SQL scripts: " + ObjectUtils.nullSafeToString(scriptResources));
|
logger.debug("Executing SQL scripts: " + ObjectUtils.nullSafeToString(scriptResources));
|
||||||
|
@ -225,13 +258,23 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
|
||||||
TransactionDefinition.PROPAGATION_REQUIRED);
|
TransactionDefinition.PROPAGATION_REQUIRED);
|
||||||
TransactionAttribute txAttr = TestContextTransactionUtils.createDelegatingTransactionAttribute(
|
TransactionAttribute txAttr = TestContextTransactionUtils.createDelegatingTransactionAttribute(
|
||||||
testContext, new DefaultTransactionAttribute(propagation));
|
testContext, new DefaultTransactionAttribute(propagation));
|
||||||
new TransactionTemplate(txMgr, txAttr).execute(status -> {
|
new TransactionTemplate(txMgr, txAttr).execute(() -> populator.execute(finalDataSource));
|
||||||
populator.execute(finalDataSource);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private ResourceDatabasePopulator createDatabasePopulator(MergedSqlConfig mergedSqlConfig) {
|
||||||
|
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
|
||||||
|
populator.setSqlScriptEncoding(mergedSqlConfig.getEncoding());
|
||||||
|
populator.setSeparator(mergedSqlConfig.getSeparator());
|
||||||
|
populator.setCommentPrefix(mergedSqlConfig.getCommentPrefix());
|
||||||
|
populator.setBlockCommentStartDelimiter(mergedSqlConfig.getBlockCommentStartDelimiter());
|
||||||
|
populator.setBlockCommentEndDelimiter(mergedSqlConfig.getBlockCommentEndDelimiter());
|
||||||
|
populator.setContinueOnError(mergedSqlConfig.getErrorMode() == ErrorMode.CONTINUE_ON_ERROR);
|
||||||
|
populator.setIgnoreFailedDrops(mergedSqlConfig.getErrorMode() == ErrorMode.IGNORE_FAILED_DROPS);
|
||||||
|
return populator;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private DataSource getDataSourceFromTransactionManager(PlatformTransactionManager transactionManager) {
|
private DataSource getDataSourceFromTransactionManager(PlatformTransactionManager transactionManager) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2019 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
|
||||||
|
*
|
||||||
|
* https://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.merging;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.jdbc.EmptyDatabaseConfig;
|
||||||
|
import org.springframework.test.context.jdbc.SqlMergeMode;
|
||||||
|
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.springframework.test.annotation.DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract base class for tests involving {@link SqlMergeMode @SqlMergeMode}.
|
||||||
|
*
|
||||||
|
* @author Sam Brannen
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
@ContextConfiguration(classes = EmptyDatabaseConfig.class)
|
||||||
|
@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD)
|
||||||
|
abstract class AbstractSqlMergeModeTests extends AbstractTransactionalJUnit4SpringContextTests {
|
||||||
|
|
||||||
|
protected void assertUsers(String... expectedUsers) {
|
||||||
|
List<String> actualUsers = super.jdbcTemplate.queryForList("select name from user", String.class);
|
||||||
|
assertThat(actualUsers).containsExactlyInAnyOrder(expectedUsers);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2019 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
|
||||||
|
*
|
||||||
|
* https://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.merging;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.test.context.jdbc.Sql;
|
||||||
|
import org.springframework.test.context.jdbc.SqlMergeMode;
|
||||||
|
|
||||||
|
import static org.springframework.test.context.jdbc.SqlMergeMode.MergeMode.MERGE;
|
||||||
|
import static org.springframework.test.context.jdbc.SqlMergeMode.MergeMode.OVERRIDE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transactional integration tests that verify proper merging and overriding support
|
||||||
|
* for class-level and method-level {@link Sql @Sql} declarations when
|
||||||
|
* {@link SqlMergeMode @SqlMergeMode} is declared at the class level with
|
||||||
|
* {@link SqlMergeMode.MergeMode#MERGE MERGE} mode.
|
||||||
|
*
|
||||||
|
* @author Sam Brannen
|
||||||
|
* @author Dmitry Semukhin
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
@Sql({ "../schema.sql", "../data-add-catbert.sql" })
|
||||||
|
@SqlMergeMode(MERGE)
|
||||||
|
public class ClassLevelMergeSqlMergeModeTests extends AbstractSqlMergeModeTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void classLevelScripts() {
|
||||||
|
assertUsers("Catbert");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Sql("../data-add-dogbert.sql")
|
||||||
|
public void merged() {
|
||||||
|
assertUsers("Catbert", "Dogbert");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Sql({ "../schema.sql", "../data.sql", "../data-add-dogbert.sql", "../data-add-catbert.sql" })
|
||||||
|
@SqlMergeMode(OVERRIDE)
|
||||||
|
public void overridden() {
|
||||||
|
assertUsers("Dilbert", "Dogbert", "Catbert");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2019 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
|
||||||
|
*
|
||||||
|
* https://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.merging;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.test.context.jdbc.Sql;
|
||||||
|
import org.springframework.test.context.jdbc.SqlMergeMode;
|
||||||
|
|
||||||
|
import static org.springframework.test.context.jdbc.SqlMergeMode.MergeMode.MERGE;
|
||||||
|
import static org.springframework.test.context.jdbc.SqlMergeMode.MergeMode.OVERRIDE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transactional integration tests that verify proper merging and overriding support
|
||||||
|
* for class-level and method-level {@link Sql @Sql} declarations when
|
||||||
|
* {@link SqlMergeMode @SqlMergeMode} is declared at the class level with
|
||||||
|
* {@link SqlMergeMode.MergeMode#OVERRIDE OVERRIDE} mode.
|
||||||
|
*
|
||||||
|
* @author Sam Brannen
|
||||||
|
* @author Dmitry Semukhin
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
@Sql({ "../schema.sql", "../data-add-catbert.sql" })
|
||||||
|
@SqlMergeMode(OVERRIDE)
|
||||||
|
public class ClassLevelOverrideSqlMergeModeTests extends AbstractSqlMergeModeTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void classLevelScripts() {
|
||||||
|
assertUsers("Catbert");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Sql("../data-add-dogbert.sql")
|
||||||
|
@SqlMergeMode(MERGE)
|
||||||
|
public void merged() {
|
||||||
|
assertUsers("Catbert", "Dogbert");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Sql({ "../schema.sql", "../data.sql", "../data-add-dogbert.sql", "../data-add-catbert.sql" })
|
||||||
|
public void overridden() {
|
||||||
|
assertUsers("Dilbert", "Dogbert", "Catbert");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -419,6 +419,7 @@ Spring's testing annotations include the following:
|
||||||
* <<spring-testing-annotation-aftertransaction>>
|
* <<spring-testing-annotation-aftertransaction>>
|
||||||
* <<spring-testing-annotation-sql>>
|
* <<spring-testing-annotation-sql>>
|
||||||
* <<spring-testing-annotation-sqlconfig>>
|
* <<spring-testing-annotation-sqlconfig>>
|
||||||
|
* <<spring-testing-annotation-sqlmergemode>>
|
||||||
* <<spring-testing-annotation-sqlgroup>>
|
* <<spring-testing-annotation-sqlgroup>>
|
||||||
|
|
||||||
[[spring-testing-annotation-bootstrapwith]]
|
[[spring-testing-annotation-bootstrapwith]]
|
||||||
|
@ -968,9 +969,9 @@ it:
|
||||||
----
|
----
|
||||||
<1> Run two scripts for this test.
|
<1> Run two scripts for this test.
|
||||||
|
|
||||||
|
|
||||||
See <<testcontext-executing-sql-declaratively>> for further details.
|
See <<testcontext-executing-sql-declaratively>> for further details.
|
||||||
|
|
||||||
|
|
||||||
[[spring-testing-annotation-sqlconfig]]
|
[[spring-testing-annotation-sqlconfig]]
|
||||||
===== `@SqlConfig`
|
===== `@SqlConfig`
|
||||||
|
|
||||||
|
@ -992,6 +993,56 @@ configured with the `@Sql` annotation. The following example shows how to use it
|
||||||
<1> Set the comment prefix and the separator in SQL scripts.
|
<1> Set the comment prefix and the separator in SQL scripts.
|
||||||
|
|
||||||
|
|
||||||
|
[[spring-testing-annotation-sqlmergemode]]
|
||||||
|
===== `@SqlMergeMode`
|
||||||
|
|
||||||
|
`@SqlMergeMode` is used to annotate a test class or test method to configure whether
|
||||||
|
method-level `@Sql` declarations are merged with class-level `@Sql` declarations. If
|
||||||
|
`@SqlMergeMode` is not declared on a test class or test method, the `OVERRIDE` merge mode
|
||||||
|
will be used by default. With the `OVERRIDE` mode, method-level `@Sql` declarations will
|
||||||
|
effectively override class-level `@Sql` declarations.
|
||||||
|
|
||||||
|
Note that a method-level `@SqlMergeMode` declaration overrides a class-level declaration.
|
||||||
|
|
||||||
|
The following example shows how to use `@SqlMergeMode` at the class level.
|
||||||
|
|
||||||
|
[source,java,indent=0]
|
||||||
|
[subs="verbatim,quotes"]
|
||||||
|
----
|
||||||
|
@SpringJUnitConfig(TestConfig.class)
|
||||||
|
@Sql("/test-schema.sql")
|
||||||
|
@SqlMergeMode(MERGE) <1>
|
||||||
|
class UserTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Sql("/user-test-data-001.sql")
|
||||||
|
void standardUserProfile {
|
||||||
|
// execute code that relies on test data set 001
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
<1> Set the `@Sql` merge mode to `MERGE` for all test methods in the class.
|
||||||
|
|
||||||
|
The following example shows how to use `@SqlMergeMode` at the method level.
|
||||||
|
|
||||||
|
[source,java,indent=0]
|
||||||
|
[subs="verbatim,quotes"]
|
||||||
|
----
|
||||||
|
@SpringJUnitConfig(TestConfig.class)
|
||||||
|
@Sql("/test-schema.sql")
|
||||||
|
class UserTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Sql("/user-test-data-001.sql")
|
||||||
|
@SqlMergeMode(MERGE) <1>
|
||||||
|
void standardUserProfile {
|
||||||
|
// execute code that relies on test data set 001
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
<1> Set the `@Sql` merge mode to `MERGE` for a specific test method.
|
||||||
|
|
||||||
|
|
||||||
[[spring-testing-annotation-sqlgroup]]
|
[[spring-testing-annotation-sqlgroup]]
|
||||||
===== `@SqlGroup`
|
===== `@SqlGroup`
|
||||||
|
|
||||||
|
@ -1411,6 +1462,7 @@ You can use each of the following as a meta-annotation in conjunction with the
|
||||||
* `@Rollback`
|
* `@Rollback`
|
||||||
* `@Sql`
|
* `@Sql`
|
||||||
* `@SqlConfig`
|
* `@SqlConfig`
|
||||||
|
* `@SqlMergeMode`
|
||||||
* `@SqlGroup`
|
* `@SqlGroup`
|
||||||
* `@Repeat` _(only supported on JUnit 4)_
|
* `@Repeat` _(only supported on JUnit 4)_
|
||||||
* `@Timed` _(only supported on JUnit 4)_
|
* `@Timed` _(only supported on JUnit 4)_
|
||||||
|
@ -3997,11 +4049,16 @@ various `executeSqlScript(..)` methods for further details.
|
||||||
In addition to the aforementioned mechanisms for running SQL scripts programmatically,
|
In addition to the aforementioned mechanisms for running SQL scripts programmatically,
|
||||||
you can declaratively configure SQL scripts in the Spring TestContext Framework.
|
you can declaratively configure SQL scripts in the Spring TestContext Framework.
|
||||||
Specifically, you can declare the `@Sql` annotation on a test class or test method to
|
Specifically, you can declare the `@Sql` annotation on a test class or test method to
|
||||||
configure the resource paths to SQL scripts that should be run against a given database
|
configure individual SQL statements or the resource paths to SQL scripts that should be
|
||||||
before or after an integration test method. Note that method-level declarations override
|
run against a given database before or after an integration test method. Support for
|
||||||
class-level declarations and that support for `@Sql` is provided by the
|
`@Sql` is provided by the `SqlScriptsTestExecutionListener`, which is enabled by default.
|
||||||
`SqlScriptsTestExecutionListener`, which is enabled by default.
|
|
||||||
|
|
||||||
|
NOTE: Method-level `@Sql` declarations override class-level declarations by default. As
|
||||||
|
of Spring Framework 5.2, however, this behavior may be configured per test class or per
|
||||||
|
test method via `@SqlMergeMode`. See
|
||||||
|
<<testcontext-executing-sql-declaratively-script-merging>> for further details.
|
||||||
|
|
||||||
|
[[testcontext-executing-sql-declaratively-script-resources]]
|
||||||
====== Path Resource Semantics
|
====== Path Resource Semantics
|
||||||
|
|
||||||
Each path is interpreted as a Spring `Resource`. A plain path (for example,
|
Each path is interpreted as a Spring `Resource`. A plain path (for example,
|
||||||
|
@ -4034,10 +4091,11 @@ within a JUnit Jupiter based integration test class:
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
|
[[testcontext-executing-sql-declaratively-script-detection]]
|
||||||
====== Default Script Detection
|
====== Default Script Detection
|
||||||
|
|
||||||
If no SQL scripts are specified, an attempt is made to detect a `default` script,
|
If no SQL scripts or statements are specified, an attempt is made to detect a `default`
|
||||||
depending on where `@Sql` is declared. If a default cannot be detected, an
|
script, depending on where `@Sql` is declared. If a default cannot be detected, an
|
||||||
`IllegalStateException` is thrown.
|
`IllegalStateException` is thrown.
|
||||||
|
|
||||||
* Class-level declaration: If the annotated test class is `com.example.MyTest`, the
|
* Class-level declaration: If the annotated test class is `com.example.MyTest`, the
|
||||||
|
@ -4046,6 +4104,7 @@ depending on where `@Sql` is declared. If a default cannot be detected, an
|
||||||
defined in the class `com.example.MyTest`, the corresponding default script is
|
defined in the class `com.example.MyTest`, the corresponding default script is
|
||||||
`classpath:com/example/MyTest.testMethod.sql`.
|
`classpath:com/example/MyTest.testMethod.sql`.
|
||||||
|
|
||||||
|
[[testcontext-executing-sql-declaratively-multiple-annotations]]
|
||||||
====== Declaring Multiple `@Sql` Sets
|
====== Declaring Multiple `@Sql` Sets
|
||||||
|
|
||||||
If you need to configure multiple sets of SQL scripts for a given test class or test
|
If you need to configure multiple sets of SQL scripts for a given test class or test
|
||||||
|
@ -4088,6 +4147,7 @@ Java 7.
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
|
[[testcontext-executing-sql-declaratively-script-execution-phases]]
|
||||||
====== Script Execution Phases
|
====== Script Execution Phases
|
||||||
|
|
||||||
By default, SQL scripts are executed before the corresponding test method. However, if
|
By default, SQL scripts are executed before the corresponding test method. However, if
|
||||||
|
@ -4117,6 +4177,7 @@ following example shows:
|
||||||
Note that `ISOLATED` and `AFTER_TEST_METHOD` are statically imported from
|
Note that `ISOLATED` and `AFTER_TEST_METHOD` are statically imported from
|
||||||
`Sql.TransactionMode` and `Sql.ExecutionPhase`, respectively.
|
`Sql.TransactionMode` and `Sql.ExecutionPhase`, respectively.
|
||||||
|
|
||||||
|
[[testcontext-executing-sql-declaratively-script-configuration]]
|
||||||
====== Script Configuration with `@SqlConfig`
|
====== Script Configuration with `@SqlConfig`
|
||||||
|
|
||||||
You can configure script parsing and error handling by using the `@SqlConfig` annotation.
|
You can configure script parsing and error handling by using the `@SqlConfig` annotation.
|
||||||
|
@ -4142,8 +4203,6 @@ provided by the `<jdbc:initialize-database/>` XML namespace element. See the jav
|
||||||
individual attributes in {api-spring-framework}/test/context/jdbc/Sql.html[`@Sql`] and
|
individual attributes in {api-spring-framework}/test/context/jdbc/Sql.html[`@Sql`] and
|
||||||
{api-spring-framework}/test/context/jdbc/SqlConfig.html[`@SqlConfig`] for details.
|
{api-spring-framework}/test/context/jdbc/SqlConfig.html[`@SqlConfig`] for details.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[[testcontext-executing-sql-declaratively-tx]]
|
[[testcontext-executing-sql-declaratively-tx]]
|
||||||
*Transaction management for `@Sql`*
|
*Transaction management for `@Sql`*
|
||||||
|
|
||||||
|
@ -4208,6 +4267,18 @@ run, since any changes made to the database (either within the test method or wi
|
||||||
`TransactionalTestExecutionListener` (see <<testcontext-tx,transaction management>> for
|
`TransactionalTestExecutionListener` (see <<testcontext-tx,transaction management>> for
|
||||||
details).
|
details).
|
||||||
|
|
||||||
|
[[testcontext-executing-sql-declaratively-script-merging]]
|
||||||
|
====== Merging and Overriding Configuration with `@SqlMergeMode`
|
||||||
|
|
||||||
|
As of Spring Framework 5.2, it is possible to merge method-level `@Sql` declarations with
|
||||||
|
class-level declarations. For example, this allows you to provide the configuration for a
|
||||||
|
database schema or some common test data once per test class and then provide additional,
|
||||||
|
use case specific test data per test method. To enable `@Sql` merging, annotate either
|
||||||
|
your test class or test method with `@SqlMergeMode(MERGE)`. To disable merging for a
|
||||||
|
specific test method (or specific test subclass), you can switch back to the default mode
|
||||||
|
via `@SqlMergeMode(OVERRIDE)`. Consult the <<spring-testing-annotation-sqlmergemode,
|
||||||
|
`@SqlMergeMode` annotation documentation section>> for examples and further details.
|
||||||
|
|
||||||
|
|
||||||
[[testcontext-parallel-test-execution]]
|
[[testcontext-parallel-test-execution]]
|
||||||
==== Parallel Test Execution
|
==== Parallel Test Execution
|
||||||
|
|
Loading…
Reference in New Issue