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 d1348ac13a2..fb69e925c0b 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 00000000000..352eb68da97 --- /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 06751e0f645..f824a363c85 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); + } + } + } + + /** + * 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 {@link Sql @Sql} annotations declared on the supplied - * {@link AnnotatedElement}. + * Get the {@code @Sql} annotations declared on the supplied {@code element}. */ - private Set getSqlAnnotationsFor(AnnotatedElement annotatedElement) { - return AnnotatedElementUtils.getMergedRepeatableAnnotations(annotatedElement, Sql.class, SqlGroup.class); + 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 a33731dcd5d..00000000000 --- 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 453c44af762..bc54dc2ecbc 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 00000000000..6b79558e144 --- /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 00000000000..f5464bdbedc --- /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"); + } + +}