From d77b715d38c856202d1d20d5b4ef42e05b8faea5 Mon Sep 17 00:00:00 2001 From: asympro Date: Fri, 25 May 2018 16:33:41 +0300 Subject: [PATCH 1/4] Merge class-level and method-level @Sql declarations See gh-1835 --- .../test/context/jdbc/Sql.java | 29 ++++++++- .../jdbc/SqlScriptsTestExecutionListener.java | 63 +++++++++++++------ ...epeatableSqlAnnotationSqlScriptsTests.java | 5 ++ .../test/context/jdbc/SqlMethodMergeTest.java | 30 +++++++++ .../context/jdbc/SqlMethodOverrideTest.java | 30 +++++++++ 5 files changed, 137 insertions(+), 20 deletions(-) create mode 100644 spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodMergeTest.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodOverrideTest.java diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java index 97ba3a428d..27e7e29248 100644 --- a/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java +++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java @@ -31,7 +31,8 @@ import org.springframework.core.annotation.AliasFor; * SQL {@link #scripts} and {@link #statements} to be executed against a given * database during integration tests. * - *

Method-level declarations override class-level declarations. + *

Method-level declarations override class-level declarations by default. + * This behaviour can be adjusted via {@link MergeMode} * *

Script execution is performed by the {@link SqlScriptsTestExecutionListener}, * which is enabled by default. @@ -146,6 +147,13 @@ public @interface Sql { */ SqlConfig config() default @SqlConfig; + /** + * Indicates whether this annotation should be merged with upper-level annotations + * or override them. + *

Defaults to {@link MergeMode#OVERRIDE}. + */ + MergeMode mergeMode() default MergeMode.OVERRIDE; + /** * Enumeration of phases that dictate when SQL scripts are executed. @@ -165,4 +173,23 @@ public @interface Sql { AFTER_TEST_METHOD } + /** + * Enumeration of modes that dictate whether or not + * declared SQL {@link #scripts} and {@link #statements} are merged + * with the upper-level annotations. + */ + enum MergeMode { + + /** + * Indicates that locally declared SQL {@link #scripts} and {@link #statements} + * should override the upper-level (e.g. Class-level) annotations. + */ + OVERRIDE, + + /** + * Indicates that locally declared SQL {@link #scripts} and {@link #statements} + * should be merged the upper-level (e.g. Class-level) annotations. + */ + MERGE + } } diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java index 299df20fd6..0ad5919761 100644 --- a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java @@ -16,14 +16,17 @@ package org.springframework.test.context.jdbc; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jetbrains.annotations.NotNull; import org.springframework.context.ApplicationContext; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.io.ByteArrayResource; @@ -126,19 +129,35 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen * {@link TestContext} and {@link ExecutionPhase}. */ private void executeSqlScripts(TestContext testContext, ExecutionPhase executionPhase) throws Exception { - boolean classLevel = false; - - Set sqlAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations( - testContext.getTestMethod(), Sql.class, SqlGroup.class); - if (sqlAnnotations.isEmpty()) { - sqlAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations( - testContext.getTestClass(), Sql.class, SqlGroup.class); - if (!sqlAnnotations.isEmpty()) { - classLevel = true; - } + Set methodLevelSqls = getScriptsFromElement(testContext.getTestMethod()); + List methodLevelOverrides = methodLevelSqls.stream() + .filter(s -> s.executionPhase() == executionPhase) + .filter(s -> s.mergeMode() == Sql.MergeMode.OVERRIDE) + .collect(Collectors.toList()); + if (methodLevelOverrides.isEmpty()) { + executeScripts(getScriptsFromElement(testContext.getTestClass()), testContext, executionPhase, true); + executeScripts(methodLevelSqls, testContext, executionPhase, false); + } else { + executeScripts(methodLevelOverrides, testContext, executionPhase, false); } + } - for (Sql sql : sqlAnnotations) { + /** + * Get SQL scripts configured via {@link Sql @Sql} for the supplied + * {@link AnnotatedElement}. + */ + private Set getScriptsFromElement(AnnotatedElement annotatedElement) throws Exception { + return AnnotatedElementUtils.getMergedRepeatableAnnotations(annotatedElement, Sql.class, SqlGroup.class); + } + + /** + * Execute given {@link Sql @Sql} scripts. + * {@link AnnotatedElement}. + */ + private void executeScripts(Iterable scripts, TestContext testContext, ExecutionPhase executionPhase, + boolean classLevel) + throws Exception { + for (Sql sql : scripts) { executeSqlScripts(sql, executionPhase, testContext, classLevel); } } @@ -166,14 +185,7 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen 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); + final ResourceDatabasePopulator populator = configurePopulator(mergedSqlConfig); String[] scripts = getScripts(sql, testContext, classLevel); scripts = TestContextResourceUtils.convertToClasspathResourcePaths(testContext.getTestClass(), scripts); @@ -232,6 +244,19 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen } } + @NotNull + private ResourceDatabasePopulator configurePopulator(MergedSqlConfig mergedSqlConfig) { + 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); + return populator; + } + @Nullable private DataSource getDataSourceFromTransactionManager(PlatformTransactionManager transactionManager) { try { diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/RepeatableSqlAnnotationSqlScriptsTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/RepeatableSqlAnnotationSqlScriptsTests.java index c1b827a1b1..cbc6944a31 100644 --- a/spring-test/src/test/java/org/springframework/test/context/jdbc/RepeatableSqlAnnotationSqlScriptsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/RepeatableSqlAnnotationSqlScriptsTests.java @@ -25,6 +25,7 @@ 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 org.springframework.test.jdbc.JdbcTestUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -58,6 +59,10 @@ public class RepeatableSqlAnnotationSqlScriptsTests extends AbstractTransactiona assertNumUsers(2); } + protected int countRowsInTable(String tableName) { + return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName); + } + protected void assertNumUsers(int expected) { assertThat(countRowsInTable("user")).as("Number of rows in the 'user' table.").isEqualTo(expected); } diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodMergeTest.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodMergeTest.java new file mode 100644 index 0000000000..3a23a9db87 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodMergeTest.java @@ -0,0 +1,30 @@ +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.assertEquals; + +/** + * Test to verify method level merge of @Sql annotations. + * + * @author Dmitry Semukhin + */ +@ContextConfiguration(classes = EmptyDatabaseConfig.class) +@Sql(value = {"schema.sql", "data-add-catbert.sql"}) +@DirtiesContext +public class SqlMethodMergeTest extends AbstractTransactionalJUnit4SpringContextTests { + + @Test + @Sql(value = "data-add-dogbert.sql", mergeMode = Sql.MergeMode.MERGE) + public void testMerge() { + assertNumUsers(2); + } + + protected void assertNumUsers(int expected) { + assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user")); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodOverrideTest.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodOverrideTest.java new file mode 100644 index 0000000000..6f4d8023c4 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodOverrideTest.java @@ -0,0 +1,30 @@ +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.assertEquals; + +/** + * Test to verify method level override of @Sql annotations. + * + * @author Dmitry Semukhin + */ +@ContextConfiguration(classes = EmptyDatabaseConfig.class) +@Sql(value = {"schema.sql", "data-add-catbert.sql"}) +@DirtiesContext +public class SqlMethodOverrideTest extends AbstractTransactionalJUnit4SpringContextTests { + + @Test + @Sql(value = {"schema.sql", "data.sql", "data-add-dogbert.sql", "data-add-catbert.sql"}, mergeMode = Sql.MergeMode.OVERRIDE) + public void testMerge() { + assertNumUsers(3); + } + + protected void assertNumUsers(int expected) { + assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user")); + } + +} From ab8876219f8590cb1927d0a867d58b6d2acd5c92 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 19 Jul 2019 17:14:07 +0200 Subject: [PATCH 2/4] Polish contribution See gh-1835 --- .../test/context/jdbc/Sql.java | 37 +++++++------ .../jdbc/SqlScriptsTestExecutionListener.java | 51 +++++++++--------- ...epeatableSqlAnnotationSqlScriptsTests.java | 5 -- .../test/context/jdbc/SqlMethodMergeTest.java | 30 ----------- .../context/jdbc/SqlMethodMergeTests.java | 51 ++++++++++++++++++ .../context/jdbc/SqlMethodOverrideTest.java | 30 ----------- .../context/jdbc/SqlMethodOverrideTests.java | 54 +++++++++++++++++++ 7 files changed, 150 insertions(+), 108 deletions(-) delete mode 100644 spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodMergeTest.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodMergeTests.java delete mode 100644 spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodOverrideTest.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodOverrideTests.java diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java index 27e7e29248..d1348ac13a 100644 --- a/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java +++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java @@ -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"); * you may not use this file except in compliance with the License. @@ -32,7 +32,7 @@ import org.springframework.core.annotation.AliasFor; * database during integration tests. * *

Method-level declarations override class-level declarations by default. - * This behaviour can be adjusted via {@link MergeMode} + * This behavior can be adjusted by setting the {@link #mergeMode}. * *

Script execution is performed by the {@link SqlScriptsTestExecutionListener}, * which is enabled by default. @@ -54,6 +54,7 @@ import org.springframework.core.annotation.AliasFor; * composed annotations with attribute overrides. * * @author Sam Brannen + * @author Dmitry Semukhin * @since 4.1 * @see SqlConfig * @see SqlGroup @@ -138,6 +139,16 @@ public @interface Sql { */ ExecutionPhase executionPhase() default ExecutionPhase.BEFORE_TEST_METHOD; + /** + * Indicates whether this {@code @Sql} annotation should be merged with + * class-level {@code @Sql} annotations or override them. + *

The merge mode is ignored if declared in a class-level {@code @Sql} + * annotation. + *

Defaults to {@link MergeMode#OVERRIDE OVERRIDE} for backwards compatibility. + * @since 5.2 + */ + MergeMode mergeMode() default MergeMode.OVERRIDE; + /** * Local configuration for the SQL scripts and statements declared within * this {@code @Sql} annotation. @@ -147,13 +158,6 @@ public @interface Sql { */ SqlConfig config() default @SqlConfig; - /** - * Indicates whether this annotation should be merged with upper-level annotations - * or override them. - *

Defaults to {@link MergeMode#OVERRIDE}. - */ - MergeMode mergeMode() default MergeMode.OVERRIDE; - /** * Enumeration of phases that dictate when SQL scripts are executed. @@ -174,22 +178,23 @@ public @interface Sql { } /** - * Enumeration of modes that dictate whether or not - * declared SQL {@link #scripts} and {@link #statements} are merged - * with the upper-level annotations. + * Enumeration of modes that dictate whether method-level {@code @Sql} + * declarations are merged with class-level {@code @Sql} declarations. + * @since 5.2 */ enum MergeMode { /** - * Indicates that locally declared SQL {@link #scripts} and {@link #statements} - * should override the upper-level (e.g. Class-level) annotations. + * Indicates that method-level {@code @Sql} declarations should override + * class-level {@code @Sql} declarations. */ OVERRIDE, /** - * Indicates that locally declared SQL {@link #scripts} and {@link #statements} - * should be merged the upper-level (e.g. Class-level) annotations. + * Indicates that method-level {@code @Sql} declarations should be merged + * with class-level {@code @Sql} declarations. */ MERGE } + } diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java index 0ad5919761..06751e0f64 100644 --- a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java @@ -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"); * you may not use this file except in compliance with the License. @@ -26,13 +26,13 @@ import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.jetbrains.annotations.NotNull; import org.springframework.context.ApplicationContext; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; +import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.test.context.TestContext; import org.springframework.test.context.jdbc.Sql.ExecutionPhase; @@ -84,6 +84,7 @@ import org.springframework.util.StringUtils; * locate these beans. * * @author Sam Brannen + * @author Dmitry Semukhin * @since 4.1 * @see Sql * @see SqlConfig @@ -111,7 +112,7 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen * {@link TestContext} before the current test method. */ @Override - public void beforeTestMethod(TestContext testContext) throws Exception { + public void beforeTestMethod(TestContext testContext) { executeSqlScripts(testContext, ExecutionPhase.BEFORE_TEST_METHOD); } @@ -120,7 +121,7 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen * {@link TestContext} after the current test method. */ @Override - public void afterTestMethod(TestContext testContext) throws Exception { + public void afterTestMethod(TestContext testContext) { executeSqlScripts(testContext, ExecutionPhase.AFTER_TEST_METHOD); } @@ -128,14 +129,14 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen * Execute SQL scripts configured via {@link Sql @Sql} for the supplied * {@link TestContext} and {@link ExecutionPhase}. */ - private void executeSqlScripts(TestContext testContext, ExecutionPhase executionPhase) throws Exception { - Set methodLevelSqls = getScriptsFromElement(testContext.getTestMethod()); + private void executeSqlScripts(TestContext testContext, ExecutionPhase executionPhase) { + Set methodLevelSqls = getSqlAnnotationsFor(testContext.getTestMethod()); List methodLevelOverrides = methodLevelSqls.stream() - .filter(s -> s.executionPhase() == executionPhase) - .filter(s -> s.mergeMode() == Sql.MergeMode.OVERRIDE) - .collect(Collectors.toList()); + .filter(s -> s.executionPhase() == executionPhase) + .filter(s -> s.mergeMode() == Sql.MergeMode.OVERRIDE) + .collect(Collectors.toList()); if (methodLevelOverrides.isEmpty()) { - executeScripts(getScriptsFromElement(testContext.getTestClass()), testContext, executionPhase, true); + executeScripts(getSqlAnnotationsFor(testContext.getTestClass()), testContext, executionPhase, true); executeScripts(methodLevelSqls, testContext, executionPhase, false); } else { executeScripts(methodLevelOverrides, testContext, executionPhase, false); @@ -143,20 +144,19 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen } /** - * Get SQL scripts configured via {@link Sql @Sql} for the supplied + * Get the {@link Sql @Sql} annotations declared on the supplied * {@link AnnotatedElement}. */ - private Set getScriptsFromElement(AnnotatedElement annotatedElement) throws Exception { + private Set getSqlAnnotationsFor(AnnotatedElement annotatedElement) { return AnnotatedElementUtils.getMergedRepeatableAnnotations(annotatedElement, Sql.class, SqlGroup.class); } /** - * Execute given {@link Sql @Sql} scripts. - * {@link AnnotatedElement}. + * Execute SQL scripts for the supplied {@link Sql @Sql} annotations. */ - private void executeScripts(Iterable scripts, TestContext testContext, ExecutionPhase executionPhase, - boolean classLevel) - throws Exception { + private void executeScripts( + Iterable scripts, TestContext testContext, ExecutionPhase executionPhase, boolean classLevel) { + for (Sql sql : scripts) { executeSqlScripts(sql, executionPhase, testContext, classLevel); } @@ -172,8 +172,8 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen * @param testContext the current {@code TestContext} * @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) - throws Exception { + private void executeSqlScripts( + Sql sql, ExecutionPhase executionPhase, TestContext testContext, boolean classLevel) { if (executionPhase != sql.executionPhase()) { return; @@ -185,8 +185,6 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen mergedSqlConfig, executionPhase, testContext)); } - final ResourceDatabasePopulator populator = configurePopulator(mergedSqlConfig); - String[] scripts = getScripts(sql, testContext, classLevel); scripts = TestContextResourceUtils.convertToClasspathResourcePaths(testContext.getTestClass(), scripts); List scriptResources = TestContextResourceUtils.convertToResourceList( @@ -197,6 +195,8 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen scriptResources.add(new ByteArrayResource(stmt.getBytes(), "from inlined SQL statement: " + stmt)); } } + + ResourceDatabasePopulator populator = configurePopulator(mergedSqlConfig); populator.setScripts(scriptResources.toArray(new Resource[0])); if (logger.isDebugEnabled()) { logger.debug("Executing SQL scripts: " + ObjectUtils.nullSafeToString(scriptResources)); @@ -237,16 +237,13 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen TransactionDefinition.PROPAGATION_REQUIRED); TransactionAttribute txAttr = TestContextTransactionUtils.createDelegatingTransactionAttribute( testContext, new DefaultTransactionAttribute(propagation)); - new TransactionTemplate(txMgr, txAttr).execute(status -> { - populator.execute(finalDataSource); - return null; - }); + new TransactionTemplate(txMgr, txAttr).execute(() -> populator.execute(finalDataSource)); } } - @NotNull + @NonNull private ResourceDatabasePopulator configurePopulator(MergedSqlConfig mergedSqlConfig) { - final ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); + ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); populator.setSqlScriptEncoding(mergedSqlConfig.getEncoding()); populator.setSeparator(mergedSqlConfig.getSeparator()); populator.setCommentPrefix(mergedSqlConfig.getCommentPrefix()); diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/RepeatableSqlAnnotationSqlScriptsTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/RepeatableSqlAnnotationSqlScriptsTests.java index cbc6944a31..c1b827a1b1 100644 --- a/spring-test/src/test/java/org/springframework/test/context/jdbc/RepeatableSqlAnnotationSqlScriptsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/RepeatableSqlAnnotationSqlScriptsTests.java @@ -25,7 +25,6 @@ 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 org.springframework.test.jdbc.JdbcTestUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -59,10 +58,6 @@ public class RepeatableSqlAnnotationSqlScriptsTests extends AbstractTransactiona assertNumUsers(2); } - protected int countRowsInTable(String tableName) { - return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName); - } - protected void assertNumUsers(int expected) { assertThat(countRowsInTable("user")).as("Number of rows in the 'user' table.").isEqualTo(expected); } diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodMergeTest.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodMergeTest.java deleted file mode 100644 index 3a23a9db87..0000000000 --- a/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodMergeTest.java +++ /dev/null @@ -1,30 +0,0 @@ -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.assertEquals; - -/** - * Test to verify method level merge of @Sql annotations. - * - * @author Dmitry Semukhin - */ -@ContextConfiguration(classes = EmptyDatabaseConfig.class) -@Sql(value = {"schema.sql", "data-add-catbert.sql"}) -@DirtiesContext -public class SqlMethodMergeTest extends AbstractTransactionalJUnit4SpringContextTests { - - @Test - @Sql(value = "data-add-dogbert.sql", mergeMode = Sql.MergeMode.MERGE) - public void testMerge() { - assertNumUsers(2); - } - - protected void assertNumUsers(int expected) { - assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user")); - } - -} diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodMergeTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodMergeTests.java new file mode 100644 index 0000000000..453c44af76 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodMergeTests.java @@ -0,0 +1,51 @@ +/* + * 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 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.assertEquals; +import static org.springframework.test.context.jdbc.Sql.MergeMode.MERGE; + +/** + * Transactional integration tests for {@link Sql @Sql} that verify proper + * merging support for class-level and method-level declarations. + * + * @author Dmitry Semukhin + * @author Sam Brannen + * @since 5.2 + */ +@ContextConfiguration(classes = EmptyDatabaseConfig.class) +@Sql({ "schema.sql", "data-add-catbert.sql" }) +@DirtiesContext +public class SqlMethodMergeTests extends AbstractTransactionalJUnit4SpringContextTests { + + @Test + @Sql(scripts = "data-add-dogbert.sql", mergeMode = MERGE) + public void testMerge() { + assertNumUsers(2); + } + + protected void assertNumUsers(int expected) { + assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user")); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodOverrideTest.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodOverrideTest.java deleted file mode 100644 index 6f4d8023c4..0000000000 --- a/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodOverrideTest.java +++ /dev/null @@ -1,30 +0,0 @@ -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.assertEquals; - -/** - * Test to verify method level override of @Sql annotations. - * - * @author Dmitry Semukhin - */ -@ContextConfiguration(classes = EmptyDatabaseConfig.class) -@Sql(value = {"schema.sql", "data-add-catbert.sql"}) -@DirtiesContext -public class SqlMethodOverrideTest extends AbstractTransactionalJUnit4SpringContextTests { - - @Test - @Sql(value = {"schema.sql", "data.sql", "data-add-dogbert.sql", "data-add-catbert.sql"}, mergeMode = Sql.MergeMode.OVERRIDE) - public void testMerge() { - assertNumUsers(3); - } - - protected void assertNumUsers(int expected) { - assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user")); - } - -} diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodOverrideTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodOverrideTests.java new file mode 100644 index 0000000000..a33731dcd5 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodOverrideTests.java @@ -0,0 +1,54 @@ +/* + * 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 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.assertEquals; +import static org.springframework.test.context.jdbc.Sql.MergeMode.OVERRIDE; + +/** + * Transactional integration tests for {@link Sql @Sql} that verify proper + * overriding support for class-level and method-level declarations. + * + * @author Dmitry Semukhin + * @author Sam Brannen + * @since 5.2 + */ +@ContextConfiguration(classes = EmptyDatabaseConfig.class) +@Sql({ "schema.sql", "data-add-catbert.sql" }) +@DirtiesContext +public class SqlMethodOverrideTests extends AbstractTransactionalJUnit4SpringContextTests { + + @Test + @Sql( + scripts = { "schema.sql", "data.sql", "data-add-dogbert.sql", "data-add-catbert.sql" }, + mergeMode = OVERRIDE + ) + public void methodLevelSqlScriptsOverrideClassLevelScripts() { + assertNumUsers(3); + } + + protected void assertNumUsers(int expected) { + assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user")); + } + +} From 89571ea2368d2f0804b2edb4f16e7b50777cfc8a Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 19 Jul 2019 22:35:07 +0200 Subject: [PATCH 3/4] Introduce @SqlMergeMode for configuring @Sql annotation merging Closes gh-1835 --- .../test/context/jdbc/Sql.java | 36 +------- .../test/context/jdbc/SqlMergeMode.java | 82 +++++++++++++++++++ .../jdbc/SqlScriptsTestExecutionListener.java | 65 ++++++++++----- .../context/jdbc/SqlMethodOverrideTests.java | 54 ------------ .../AbstractSqlMergeModeTests.java} | 30 +++---- .../ClassLevelMergeSqlMergeModeTests.java | 59 +++++++++++++ .../ClassLevelOverrideSqlMergeModeTests.java | 59 +++++++++++++ 7 files changed, 258 insertions(+), 127 deletions(-) create mode 100644 spring-test/src/main/java/org/springframework/test/context/jdbc/SqlMergeMode.java delete mode 100644 spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodOverrideTests.java rename spring-test/src/test/java/org/springframework/test/context/jdbc/{SqlMethodMergeTests.java => merging/AbstractSqlMergeModeTests.java} (54%) create mode 100644 spring-test/src/test/java/org/springframework/test/context/jdbc/merging/ClassLevelMergeSqlMergeModeTests.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/jdbc/merging/ClassLevelOverrideSqlMergeModeTests.java diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java index d1348ac13a..fb69e925c0 100644 --- a/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java +++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java @@ -31,8 +31,8 @@ import org.springframework.core.annotation.AliasFor; * SQL {@link #scripts} and {@link #statements} to be executed against a given * database during integration tests. * - *

Method-level declarations override class-level declarations by default. - * This behavior can be adjusted by setting the {@link #mergeMode}. + *

Method-level declarations override class-level declarations by default, + * but this behavior can be configured via {@link SqlMergeMode @SqlMergeMode}. * *

Script execution is performed by the {@link SqlScriptsTestExecutionListener}, * which is enabled by default. @@ -54,9 +54,9 @@ import org.springframework.core.annotation.AliasFor; * composed annotations with attribute overrides. * * @author Sam Brannen - * @author Dmitry Semukhin * @since 4.1 * @see SqlConfig + * @see SqlMergeMode * @see SqlGroup * @see SqlScriptsTestExecutionListener * @see org.springframework.transaction.annotation.Transactional @@ -139,16 +139,6 @@ public @interface Sql { */ ExecutionPhase executionPhase() default ExecutionPhase.BEFORE_TEST_METHOD; - /** - * Indicates whether this {@code @Sql} annotation should be merged with - * class-level {@code @Sql} annotations or override them. - *

The merge mode is ignored if declared in a class-level {@code @Sql} - * annotation. - *

Defaults to {@link MergeMode#OVERRIDE OVERRIDE} for backwards compatibility. - * @since 5.2 - */ - MergeMode mergeMode() default MergeMode.OVERRIDE; - /** * Local configuration for the SQL scripts and statements declared within * this {@code @Sql} annotation. @@ -177,24 +167,4 @@ public @interface Sql { AFTER_TEST_METHOD } - /** - * Enumeration of modes that dictate whether method-level {@code @Sql} - * declarations are merged with class-level {@code @Sql} declarations. - * @since 5.2 - */ - enum MergeMode { - - /** - * Indicates that method-level {@code @Sql} declarations should override - * class-level {@code @Sql} declarations. - */ - OVERRIDE, - - /** - * Indicates that method-level {@code @Sql} declarations should be merged - * with class-level {@code @Sql} declarations. - */ - MERGE - } - } diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlMergeMode.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlMergeMode.java new file mode 100644 index 0000000000..352eb68da9 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlMergeMode.java @@ -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. + * + *

A method-level {@code @SqlMergeMode} declaration overrides a class-level + * declaration. + * + *

If {@code @SqlMergeMode} is not declared on a test class or test method, + * {@link MergeMode#OVERRIDE} will be used by default. + * + *

This annotation may be used as a meta-annotation to create custom + * composed annotations 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 modes 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 + + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java index 06751e0f64..f824a363c8 100644 --- a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java @@ -20,7 +20,6 @@ import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; import javax.sql.DataSource; import org.apache.commons.logging.Log; @@ -38,6 +37,7 @@ import org.springframework.test.context.TestContext; import org.springframework.test.context.jdbc.Sql.ExecutionPhase; import org.springframework.test.context.jdbc.SqlConfig.ErrorMode; 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.transaction.TestContextTransactionUtils; import org.springframework.test.context.util.TestContextResourceUtils; @@ -130,36 +130,57 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen * {@link TestContext} and {@link ExecutionPhase}. */ private void executeSqlScripts(TestContext testContext, ExecutionPhase executionPhase) { - Set methodLevelSqls = getSqlAnnotationsFor(testContext.getTestMethod()); - List methodLevelOverrides = methodLevelSqls.stream() - .filter(s -> s.executionPhase() == executionPhase) - .filter(s -> s.mergeMode() == Sql.MergeMode.OVERRIDE) - .collect(Collectors.toList()); - if (methodLevelOverrides.isEmpty()) { - executeScripts(getSqlAnnotationsFor(testContext.getTestClass()), testContext, executionPhase, true); - executeScripts(methodLevelSqls, testContext, executionPhase, false); - } else { - executeScripts(methodLevelOverrides, testContext, executionPhase, false); + Method testMethod = testContext.getTestMethod(); + Class testClass = testContext.getTestClass(); + + if (mergeSqlAnnotations(testContext)) { + executeSqlScripts(getSqlAnnotationsFor(testClass), testContext, executionPhase, true); + executeSqlScripts(getSqlAnnotationsFor(testMethod), testContext, executionPhase, false); + } + else { + Set methodLevelSqlAnnotations = getSqlAnnotationsFor(testMethod); + if (!methodLevelSqlAnnotations.isEmpty()) { + executeSqlScripts(methodLevelSqlAnnotations, testContext, executionPhase, false); + } + else { + executeSqlScripts(getSqlAnnotationsFor(testClass), testContext, executionPhase, true); + } } } /** - * Get the {@link Sql @Sql} annotations declared on the supplied - * {@link AnnotatedElement}. + * Determine if method-level {@code @Sql} annotations should be merged with + * class-level {@code @Sql} annotations. */ - private Set getSqlAnnotationsFor(AnnotatedElement annotatedElement) { - return AnnotatedElementUtils.getMergedRepeatableAnnotations(annotatedElement, Sql.class, SqlGroup.class); + 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 getSqlAnnotationsFor(AnnotatedElement element) { + return AnnotatedElementUtils.getMergedRepeatableAnnotations(element, Sql.class, SqlGroup.class); } /** * Execute SQL scripts for the supplied {@link Sql @Sql} annotations. */ - private void executeScripts( - Iterable scripts, TestContext testContext, ExecutionPhase executionPhase, boolean classLevel) { + private void executeSqlScripts( + Set sqlAnnotations, TestContext testContext, ExecutionPhase executionPhase, boolean classLevel) { - for (Sql sql : scripts) { - executeSqlScripts(sql, executionPhase, testContext, classLevel); - } + sqlAnnotations.forEach(sql -> executeSqlScripts(sql, executionPhase, testContext, classLevel)); } /** @@ -196,7 +217,7 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen } } - ResourceDatabasePopulator populator = configurePopulator(mergedSqlConfig); + ResourceDatabasePopulator populator = createDatabasePopulator(mergedSqlConfig); populator.setScripts(scriptResources.toArray(new Resource[0])); if (logger.isDebugEnabled()) { logger.debug("Executing SQL scripts: " + ObjectUtils.nullSafeToString(scriptResources)); @@ -242,7 +263,7 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen } @NonNull - private ResourceDatabasePopulator configurePopulator(MergedSqlConfig mergedSqlConfig) { + private ResourceDatabasePopulator createDatabasePopulator(MergedSqlConfig mergedSqlConfig) { ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); populator.setSqlScriptEncoding(mergedSqlConfig.getEncoding()); populator.setSeparator(mergedSqlConfig.getSeparator()); diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodOverrideTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodOverrideTests.java deleted file mode 100644 index a33731dcd5..0000000000 --- a/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodOverrideTests.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 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.assertEquals; -import static org.springframework.test.context.jdbc.Sql.MergeMode.OVERRIDE; - -/** - * Transactional integration tests for {@link Sql @Sql} that verify proper - * overriding support for class-level and method-level declarations. - * - * @author Dmitry Semukhin - * @author Sam Brannen - * @since 5.2 - */ -@ContextConfiguration(classes = EmptyDatabaseConfig.class) -@Sql({ "schema.sql", "data-add-catbert.sql" }) -@DirtiesContext -public class SqlMethodOverrideTests extends AbstractTransactionalJUnit4SpringContextTests { - - @Test - @Sql( - scripts = { "schema.sql", "data.sql", "data-add-dogbert.sql", "data-add-catbert.sql" }, - mergeMode = OVERRIDE - ) - public void methodLevelSqlScriptsOverrideClassLevelScripts() { - assertNumUsers(3); - } - - protected void assertNumUsers(int expected) { - assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user")); - } - -} diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodMergeTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/merging/AbstractSqlMergeModeTests.java similarity index 54% rename from spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodMergeTests.java rename to spring-test/src/test/java/org/springframework/test/context/jdbc/merging/AbstractSqlMergeModeTests.java index 453c44af76..bc54dc2ecb 100644 --- a/spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodMergeTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/merging/AbstractSqlMergeModeTests.java @@ -14,38 +14,32 @@ * limitations under the License. */ -package org.springframework.test.context.jdbc; +package org.springframework.test.context.jdbc.merging; -import org.junit.Test; +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.junit.Assert.assertEquals; -import static org.springframework.test.context.jdbc.Sql.MergeMode.MERGE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.annotation.DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD; /** - * Transactional integration tests for {@link Sql @Sql} that verify proper - * merging support for class-level and method-level declarations. + * Abstract base class for tests involving {@link SqlMergeMode @SqlMergeMode}. * - * @author Dmitry Semukhin * @author Sam Brannen * @since 5.2 */ @ContextConfiguration(classes = EmptyDatabaseConfig.class) -@Sql({ "schema.sql", "data-add-catbert.sql" }) -@DirtiesContext -public class SqlMethodMergeTests extends AbstractTransactionalJUnit4SpringContextTests { +@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) +abstract class AbstractSqlMergeModeTests extends AbstractTransactionalJUnit4SpringContextTests { - @Test - @Sql(scripts = "data-add-dogbert.sql", mergeMode = MERGE) - public void testMerge() { - assertNumUsers(2); - } - - protected void assertNumUsers(int expected) { - assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user")); + protected void assertUsers(String... expectedUsers) { + List actualUsers = super.jdbcTemplate.queryForList("select name from user", String.class); + assertThat(actualUsers).containsExactlyInAnyOrder(expectedUsers); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/merging/ClassLevelMergeSqlMergeModeTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/merging/ClassLevelMergeSqlMergeModeTests.java new file mode 100644 index 0000000000..6b79558e14 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/merging/ClassLevelMergeSqlMergeModeTests.java @@ -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"); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/merging/ClassLevelOverrideSqlMergeModeTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/merging/ClassLevelOverrideSqlMergeModeTests.java new file mode 100644 index 0000000000..f5464bdbed --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/merging/ClassLevelOverrideSqlMergeModeTests.java @@ -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"); + } + +} From ccdf04e92595f144a71b86d32dc30274f8778457 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sun, 21 Jul 2019 15:37:51 +0200 Subject: [PATCH 4/4] Document @SqlMergeMode support in reference manual See gh-1835 --- src/docs/asciidoc/testing.adoc | 89 ++++++++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 9 deletions(-) diff --git a/src/docs/asciidoc/testing.adoc b/src/docs/asciidoc/testing.adoc index 2d1098d220..d9e37f2762 100644 --- a/src/docs/asciidoc/testing.adoc +++ b/src/docs/asciidoc/testing.adoc @@ -419,6 +419,7 @@ Spring's testing annotations include the following: * <> * <> * <> +* <> * <> [[spring-testing-annotation-bootstrapwith]] @@ -968,9 +969,9 @@ it: ---- <1> Run two scripts for this test. - See <> for further details. + [[spring-testing-annotation-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. +[[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]] ===== `@SqlGroup` @@ -1411,6 +1462,7 @@ You can use each of the following as a meta-annotation in conjunction with the * `@Rollback` * `@Sql` * `@SqlConfig` +* `@SqlMergeMode` * `@SqlGroup` * `@Repeat` _(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, 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 -configure the resource paths to SQL scripts that should be run against a given database -before or after an integration test method. Note that method-level declarations override -class-level declarations and that support for `@Sql` is provided by the -`SqlScriptsTestExecutionListener`, which is enabled by default. +configure individual SQL statements or the resource paths to SQL scripts that should be +run against a given database before or after an integration test method. Support for +`@Sql` is provided by the `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 +<> for further details. + +[[testcontext-executing-sql-declaratively-script-resources]] ====== Path Resource Semantics 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 -If no SQL scripts are specified, an attempt is made to detect a `default` script, -depending on where `@Sql` is declared. If a default cannot be detected, an +If no SQL scripts or statements are specified, an attempt is made to detect a `default` +script, depending on where `@Sql` is declared. If a default cannot be detected, an `IllegalStateException` is thrown. * 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 `classpath:com/example/MyTest.testMethod.sql`. +[[testcontext-executing-sql-declaratively-multiple-annotations]] ====== Declaring Multiple `@Sql` Sets 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 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 `Sql.TransactionMode` and `Sql.ExecutionPhase`, respectively. +[[testcontext-executing-sql-declaratively-script-configuration]] ====== Script Configuration with `@SqlConfig` You can configure script parsing and error handling by using the `@SqlConfig` annotation. @@ -4142,8 +4203,6 @@ provided by the `` XML namespace element. See the jav individual attributes in {api-spring-framework}/test/context/jdbc/Sql.html[`@Sql`] and {api-spring-framework}/test/context/jdbc/SqlConfig.html[`@SqlConfig`] for details. - - [[testcontext-executing-sql-declaratively-tx]] *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 <> for 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 <> for examples and further details. + [[testcontext-parallel-test-execution]] ==== Parallel Test Execution