From b8b854db8c116feca5a1ae2323e72ed2e0dc7fc9 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 22 Oct 2020 17:01:32 +0200 Subject: [PATCH] Discover @Sql annotations on enclosing class for nested test class This commit introduces support for discovering @Sql, @SqlGroup, @SqlConfig, and @SqlMergeMode on enclosing classes for @Nested test classes in JUnit Jupiter. Closes gh-25913 --- .../test/context/NestedTestConfiguration.java | 3 + .../test/context/jdbc/MergedSqlConfig.java | 9 +- .../jdbc/SqlScriptsTestExecutionListener.java | 31 ++- .../test/util/MetaAnnotationUtils.java | 52 ++++ .../context/jdbc/MergedSqlConfigTests.java | 238 ++++++++++++------ ...ableSqlAnnotationSqlScriptsChildTests.java | 48 ++++ ...leSqlAnnotationSqlScriptsParentTests.java} | 10 +- .../merging/AbstractSqlMergeModeTests.java | 2 +- .../jupiter/nested/SqlScriptNestedTests.java | 77 +++++- src/docs/asciidoc/testing.adoc | 3 + 10 files changed, 376 insertions(+), 97 deletions(-) create mode 100644 spring-test/src/test/java/org/springframework/test/context/jdbc/RepeatableSqlAnnotationSqlScriptsChildTests.java rename spring-test/src/test/java/org/springframework/test/context/jdbc/{RepeatableSqlAnnotationSqlScriptsTests.java => RepeatableSqlAnnotationSqlScriptsParentTests.java} (87%) diff --git a/spring-test/src/main/java/org/springframework/test/context/NestedTestConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/NestedTestConfiguration.java index fba36b6501..48b4b91e9f 100644 --- a/spring-test/src/main/java/org/springframework/test/context/NestedTestConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/context/NestedTestConfiguration.java @@ -78,6 +78,9 @@ import org.springframework.lang.Nullable; *
  • {@link org.springframework.transaction.annotation.Transactional @Transactional}
  • *
  • {@link org.springframework.test.annotation.Rollback @Rollback}
  • *
  • {@link org.springframework.test.annotation.Commit @Commit}
  • + *
  • {@link org.springframework.test.context.jdbc.Sql @Sql}
  • + *
  • {@link org.springframework.test.context.jdbc.SqlConfig @SqlConfig}
  • + *
  • {@link org.springframework.test.context.jdbc.SqlMergeMode @SqlMergeMode}
  • *
  • {@link TestConstructor @TestConstructor}
  • * * diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/MergedSqlConfig.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/MergedSqlConfig.java index 72a1cab574..02e014aa8b 100644 --- a/spring-test/src/main/java/org/springframework/test/context/jdbc/MergedSqlConfig.java +++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/MergedSqlConfig.java @@ -19,7 +19,6 @@ package org.springframework.test.context.jdbc; import java.lang.reflect.Array; import java.util.Arrays; -import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.style.ToStringCreator; @@ -27,6 +26,7 @@ import org.springframework.jdbc.datasource.init.ScriptUtils; import org.springframework.lang.Nullable; import org.springframework.test.context.jdbc.SqlConfig.ErrorMode; import org.springframework.test.context.jdbc.SqlConfig.TransactionMode; +import org.springframework.test.util.MetaAnnotationUtils; import org.springframework.util.Assert; /** @@ -100,14 +100,15 @@ class MergedSqlConfig { enforceCommentPrefixAliases(localAttributes); // Get global attributes, if any. - AnnotationAttributes globalAttributes = AnnotatedElementUtils.findMergedAnnotationAttributes( - testClass, SqlConfig.class.getName(), false, false); + SqlConfig globalSqlConfig = MetaAnnotationUtils.findMergedAnnotation(testClass, SqlConfig.class); // Use local attributes only? - if (globalAttributes == null) { + if (globalSqlConfig == null) { return localAttributes; } + AnnotationAttributes globalAttributes = AnnotationUtils.getAnnotationAttributes(globalSqlConfig, false, false); + // Enforce comment prefix aliases within the global @SqlConfig. enforceCommentPrefixAliases(globalAttributes); 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 c2aaa34f2a..00028337fd 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-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -16,7 +16,6 @@ package org.springframework.test.context.jdbc; -import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.List; import java.util.Set; @@ -42,6 +41,7 @@ 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; +import org.springframework.test.util.MetaAnnotationUtils; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.interceptor.DefaultTransactionAttribute; @@ -162,18 +162,33 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen } /** - * Get the {@code @SqlMergeMode} annotation declared on the supplied {@code element}. + * Get the {@code @SqlMergeMode} annotation declared on the supplied class. */ @Nullable - private SqlMergeMode getSqlMergeModeFor(AnnotatedElement element) { - return AnnotatedElementUtils.findMergedAnnotation(element, SqlMergeMode.class); + private SqlMergeMode getSqlMergeModeFor(Class clazz) { + return MetaAnnotationUtils.findMergedAnnotation(clazz, SqlMergeMode.class); } /** - * Get the {@code @Sql} annotations declared on the supplied {@code element}. + * Get the {@code @SqlMergeMode} annotation declared on the supplied method. */ - private Set getSqlAnnotationsFor(AnnotatedElement element) { - return AnnotatedElementUtils.getMergedRepeatableAnnotations(element, Sql.class, SqlGroup.class); + @Nullable + private SqlMergeMode getSqlMergeModeFor(Method method) { + return AnnotatedElementUtils.findMergedAnnotation(method, SqlMergeMode.class); + } + + /** + * Get the {@code @Sql} annotations declared on the supplied class. + */ + private Set getSqlAnnotationsFor(Class clazz) { + return MetaAnnotationUtils.getMergedRepeatableAnnotations(clazz, Sql.class); + } + + /** + * Get the {@code @Sql} annotations declared on the supplied method. + */ + private Set getSqlAnnotationsFor(Method method) { + return AnnotatedElementUtils.getMergedRepeatableAnnotations(method, Sql.class, SqlGroup.class); } /** diff --git a/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java b/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java index 20abe3e772..01beb1b6cb 100644 --- a/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java +++ b/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java @@ -17,6 +17,7 @@ package org.springframework.test.util; import java.lang.annotation.Annotation; +import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.function.Predicate; @@ -85,12 +86,16 @@ public abstract class MetaAnnotationUtils { * {@linkplain Class#getEnclosingClass() enclosing class} hierarchy of the * supplied class. The enclosing class hierarchy will only be searched if * appropriate. + *

    {@link org.springframework.core.annotation.AliasFor @AliasFor} semantics + * are fully supported, both within a single annotation and within annotation + * hierarchies. * @param clazz the class to look for annotations on * @param annotationType the type of annotation to look for * @return the merged, synthesized {@code Annotation}, or {@code null} if not found * @since 5.3 * @see AnnotatedElementUtils#findMergedAnnotation(java.lang.reflect.AnnotatedElement, Class) * @see #findAnnotationDescriptor(Class, Class) + * @see #searchEnclosingClass(Class) */ @Nullable public static T findMergedAnnotation(Class clazz, Class annotationType) { @@ -106,6 +111,53 @@ public abstract class MetaAnnotationUtils { return (descriptor != null ? descriptor.synthesizeAnnotation() : null); } + /** + * Get all repeatable annotations of the specified {@code annotationType} + * within the annotation hierarchy above the supplied class; and for + * each annotation found, merge that annotation's attributes with matching + * attributes from annotations in lower levels of the annotation hierarchy and + * synthesize the results back into an annotation of the specified {@code annotationType}. + *

    This method will find {@link java.lang.annotation.Inherited @Inherited} + * annotations declared on superclasses if the supplied class does not have + * any local declarations of the repeatable annotation. If no inherited + * annotations are found, this method will search within the + * {@linkplain Class#getEnclosingClass() enclosing class} hierarchy of the + * supplied class. The enclosing class hierarchy will only be searched if + * appropriate. + *

    The container type that holds the repeatable annotations will be looked up + * via {@link java.lang.annotation.Repeatable}. + *

    {@link org.springframework.core.annotation.AliasFor @AliasFor} semantics + * are fully supported, both within a single annotation and within annotation + * hierarchies. + * @param clazz the class on which to search for annotations (never {@code null}) + * @param annotationType the annotation type to find (never {@code null}) + * @return the set of all merged repeatable annotations found, or an empty set + * if none were found + * @since 5.3 + * @see AnnotatedElementUtils#getMergedRepeatableAnnotations(java.lang.reflect.AnnotatedElement, Class) + * @see #searchEnclosingClass(Class) + */ + public static Set getMergedRepeatableAnnotations( + Class clazz, Class annotationType) { + + // Present (via @Inherited semantics), directly present, or meta-present? + Set mergedAnnotations = MergedAnnotations.from(clazz, SearchStrategy.INHERITED_ANNOTATIONS) + .stream(annotationType) + .collect(MergedAnnotationCollectors.toAnnotationSet()); + + if (!mergedAnnotations.isEmpty()) { + return mergedAnnotations; + } + + // Declared on an enclosing class of an inner class? + if (searchEnclosingClass(clazz)) { + // Then mimic @Inherited semantics within the enclosing class hierarchy. + return getMergedRepeatableAnnotations(clazz.getEnclosingClass(), annotationType); + } + + return Collections.emptySet(); + } + /** * Find the {@link AnnotationDescriptor} for the supplied {@code annotationType} * on the supplied {@link Class}, traversing its annotations, interfaces, and diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/MergedSqlConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/MergedSqlConfigTests.java index d9cc836356..e38d302870 100644 --- a/spring-test/src/test/java/org/springframework/test/context/jdbc/MergedSqlConfigTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/MergedSqlConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -18,6 +18,7 @@ package org.springframework.test.context.jdbc; import java.lang.reflect.Method; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -182,86 +183,62 @@ class MergedSqlConfigTests { .withMessage("You may declare the 'commentPrefix' or 'commentPrefixes' attribute in @SqlConfig but not both"); } - @Test - void globalConfigWithDefaults() throws Exception { - Method method = GlobalConfigWithDefaultsClass.class.getMethod("globalConfigMethod"); - SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config(); - MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, GlobalConfigWithDefaultsClass.class); - assertDefaults(cfg); + @Nested + class TopLevelMergedSqlConfigTests { + + @Test + void globalConfigWithDefaults() throws Exception { + assertGlobalConfigWithDefaults(GlobalConfigWithDefaultsClass.class); + } + + @Test + void globalConfig() throws Exception { + assertGlobalConfig(GlobalConfigClass.class); + } + + @Test + void globalConfigWithLocalOverrides() throws Exception { + assertGlobalConfigWithLocalOverrides(GlobalConfigClass.class); + } + + @Test + void globalConfigWithCommentPrefixAndLocalOverrides() throws Exception { + assertGlobalConfigWithCommentPrefixAndLocalOverrides(GlobalConfigWithPrefixClass.class); + } + + @Test + void globalConfigWithCommentPrefixesAndLocalOverrides() throws Exception { + assertGlobalConfigWithCommentPrefixesAndLocalOverrides(GlobalConfigWithPrefixesClass.class); + } } - @Test - void globalConfig() throws Exception { - Method method = GlobalConfigClass.class.getMethod("globalConfigMethod"); - SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config(); - MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, GlobalConfigClass.class); + @Nested + class NestedMergedSqlConfigTests { - assertSoftly(softly -> { - softly.assertThat(cfg).isNotNull(); - softly.assertThat(cfg.getDataSource()).as("dataSource").isEqualTo(""); - softly.assertThat(cfg.getTransactionManager()).as("transactionManager").isEqualTo(""); - softly.assertThat(cfg.getTransactionMode()).as("transactionMode").isEqualTo(INFERRED); - softly.assertThat(cfg.getEncoding()).as("encoding").isEqualTo("global"); - softly.assertThat(cfg.getSeparator()).as("separator").isEqualTo("\n"); - softly.assertThat(cfg.getCommentPrefixes()).as("commentPrefixes").isEqualTo(array("`", "--")); - softly.assertThat(cfg.getBlockCommentStartDelimiter()).as("blockCommentStartDelimiter").isEqualTo(DEFAULT_BLOCK_COMMENT_START_DELIMITER); - softly.assertThat(cfg.getBlockCommentEndDelimiter()).as("blockCommentEndDelimiter").isEqualTo(DEFAULT_BLOCK_COMMENT_END_DELIMITER); - softly.assertThat(cfg.getErrorMode()).as("errorMode").isEqualTo(IGNORE_FAILED_DROPS); - }); - } + @Test + void globalConfigWithDefaults() throws Exception { + assertGlobalConfigWithDefaults(GlobalConfigWithDefaultsClass.Nested.class); + } - @Test - void globalConfigWithLocalOverrides() throws Exception { - Method method = GlobalConfigClass.class.getMethod("globalConfigWithLocalOverridesMethod"); - SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config(); - MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, GlobalConfigClass.class); + @Test + void globalConfig() throws Exception { + assertGlobalConfig(GlobalConfigClass.Nested.class); + } - assertSoftly(softly -> { - softly.assertThat(cfg).isNotNull(); - softly.assertThat(cfg.getDataSource()).as("dataSource").isEqualTo(""); - softly.assertThat(cfg.getTransactionManager()).as("transactionManager").isEqualTo(""); - softly.assertThat(cfg.getTransactionMode()).as("transactionMode").isEqualTo(INFERRED); - softly.assertThat(cfg.getEncoding()).as("encoding").isEqualTo("local"); - softly.assertThat(cfg.getSeparator()).as("separator").isEqualTo("@@"); - softly.assertThat(cfg.getCommentPrefixes()).as("commentPrefixes").isEqualTo(array("#")); - softly.assertThat(cfg.getBlockCommentStartDelimiter()).as("blockCommentStartDelimiter").isEqualTo(DEFAULT_BLOCK_COMMENT_START_DELIMITER); - softly.assertThat(cfg.getBlockCommentEndDelimiter()).as("blockCommentEndDelimiter").isEqualTo(DEFAULT_BLOCK_COMMENT_END_DELIMITER); - softly.assertThat(cfg.getErrorMode()).as("errorMode").isEqualTo(CONTINUE_ON_ERROR); - }); - } + @Test + void globalConfigWithLocalOverrides() throws Exception { + assertGlobalConfigWithLocalOverrides(GlobalConfigClass.Nested.class); + } - @Test - void globalConfigWithCommentPrefixAndLocalOverrides() throws Exception { - Class testClass = GlobalConfigWithPrefixClass.class; + @Test + void globalConfigWithCommentPrefixAndLocalOverrides() throws Exception { + assertGlobalConfigWithCommentPrefixAndLocalOverrides(GlobalConfigWithPrefixClass.Nested.class); + } - Method method = testClass.getMethod("commentPrefixesOverrideCommentPrefix"); - SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config(); - MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, testClass); - - assertThat(cfg.getCommentPrefixes()).as("commentPrefixes").isEqualTo(array("#", "@")); - - method = testClass.getMethod("commentPrefixOverridesCommentPrefix"); - localSqlConfig = method.getAnnotation(Sql.class).config(); - cfg = new MergedSqlConfig(localSqlConfig, testClass); - - assertThat(cfg.getCommentPrefixes()).as("commentPrefixes").isEqualTo(array("#")); - } - - @Test - void globalConfigWithCommentPrefixesAndLocalOverrides() throws Exception { - Class testClass = GlobalConfigWithPrefixesClass.class; - - Method method = testClass.getMethod("commentPrefixesOverrideCommentPrefixes"); - SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config(); - MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, testClass); - - assertThat(cfg.getCommentPrefixes()).as("commentPrefixes").isEqualTo(array("#", "@")); - - method = testClass.getMethod("commentPrefixOverridesCommentPrefixes"); - localSqlConfig = method.getAnnotation(Sql.class).config(); - cfg = new MergedSqlConfig(localSqlConfig, testClass); - - assertThat(cfg.getCommentPrefixes()).as("commentPrefixes").isEqualTo(array("#")); + @Test + void globalConfigWithCommentPrefixesAndLocalOverrides() throws Exception { + assertGlobalConfigWithCommentPrefixesAndLocalOverrides(GlobalConfigWithPrefixesClass.Nested.class); + } } private void assertDefaults(MergedSqlConfig cfg) { @@ -279,6 +256,79 @@ class MergedSqlConfigTests { }); } + private void assertGlobalConfigWithDefaults(Class testClass) throws Exception { + Method method = testClass.getMethod("globalConfigMethod"); + SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config(); + MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, testClass); + assertDefaults(cfg); + } + + private void assertGlobalConfig(Class testClass) throws NoSuchMethodException { + Method method = testClass.getMethod("globalConfigMethod"); + SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config(); + MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, testClass); + + assertSoftly(softly -> { + softly.assertThat(cfg).isNotNull(); + softly.assertThat(cfg.getDataSource()).as("dataSource").isEqualTo(""); + softly.assertThat(cfg.getTransactionManager()).as("transactionManager").isEqualTo(""); + softly.assertThat(cfg.getTransactionMode()).as("transactionMode").isEqualTo(INFERRED); + softly.assertThat(cfg.getEncoding()).as("encoding").isEqualTo("global"); + softly.assertThat(cfg.getSeparator()).as("separator").isEqualTo("\n"); + softly.assertThat(cfg.getCommentPrefixes()).as("commentPrefixes").isEqualTo(array("`", "--")); + softly.assertThat(cfg.getBlockCommentStartDelimiter()).as("blockCommentStartDelimiter").isEqualTo(DEFAULT_BLOCK_COMMENT_START_DELIMITER); + softly.assertThat(cfg.getBlockCommentEndDelimiter()).as("blockCommentEndDelimiter").isEqualTo(DEFAULT_BLOCK_COMMENT_END_DELIMITER); + softly.assertThat(cfg.getErrorMode()).as("errorMode").isEqualTo(IGNORE_FAILED_DROPS); + }); + } + + private void assertGlobalConfigWithLocalOverrides(Class testClass) throws Exception { + Method method = testClass.getMethod("globalConfigWithLocalOverridesMethod"); + SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config(); + MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, testClass); + + assertSoftly(softly -> { + softly.assertThat(cfg).isNotNull(); + softly.assertThat(cfg.getDataSource()).as("dataSource").isEqualTo(""); + softly.assertThat(cfg.getTransactionManager()).as("transactionManager").isEqualTo(""); + softly.assertThat(cfg.getTransactionMode()).as("transactionMode").isEqualTo(INFERRED); + softly.assertThat(cfg.getEncoding()).as("encoding").isEqualTo("local"); + softly.assertThat(cfg.getSeparator()).as("separator").isEqualTo("@@"); + softly.assertThat(cfg.getCommentPrefixes()).as("commentPrefixes").isEqualTo(array("#")); + softly.assertThat(cfg.getBlockCommentStartDelimiter()).as("blockCommentStartDelimiter").isEqualTo(DEFAULT_BLOCK_COMMENT_START_DELIMITER); + softly.assertThat(cfg.getBlockCommentEndDelimiter()).as("blockCommentEndDelimiter").isEqualTo(DEFAULT_BLOCK_COMMENT_END_DELIMITER); + softly.assertThat(cfg.getErrorMode()).as("errorMode").isEqualTo(CONTINUE_ON_ERROR); + }); + } + + private void assertGlobalConfigWithCommentPrefixAndLocalOverrides(Class testClass) throws Exception { + Method method = testClass.getMethod("commentPrefixesOverrideCommentPrefix"); + SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config(); + MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, testClass); + + assertThat(cfg.getCommentPrefixes()).as("commentPrefixes").isEqualTo(array("#", "@")); + + method = testClass.getMethod("commentPrefixOverridesCommentPrefix"); + localSqlConfig = method.getAnnotation(Sql.class).config(); + cfg = new MergedSqlConfig(localSqlConfig, testClass); + + assertThat(cfg.getCommentPrefixes()).as("commentPrefixes").isEqualTo(array("#")); + } + + private void assertGlobalConfigWithCommentPrefixesAndLocalOverrides(Class testClass) throws Exception { + Method method = testClass.getMethod("commentPrefixesOverrideCommentPrefixes"); + SqlConfig localSqlConfig = method.getAnnotation(Sql.class).config(); + MergedSqlConfig cfg = new MergedSqlConfig(localSqlConfig, testClass); + + assertThat(cfg.getCommentPrefixes()).as("commentPrefixes").isEqualTo(array("#", "@")); + + method = testClass.getMethod("commentPrefixOverridesCommentPrefixes"); + localSqlConfig = method.getAnnotation(Sql.class).config(); + cfg = new MergedSqlConfig(localSqlConfig, testClass); + + assertThat(cfg.getCommentPrefixes()).as("commentPrefixes").isEqualTo(array("#")); + } + private static String[] array(String... elements) { return elements; } @@ -339,6 +389,13 @@ class MergedSqlConfigTests { @Sql public void globalConfigMethod() { } + + class Nested { + + @Sql + public void globalConfigMethod() { + } + } } @SqlConfig(encoding = "global", separator = "\n", commentPrefixes = { "`", "--" }, errorMode = IGNORE_FAILED_DROPS) @@ -351,6 +408,17 @@ class MergedSqlConfigTests { @Sql(config = @SqlConfig(encoding = "local", separator = "@@", commentPrefix = "#", errorMode = CONTINUE_ON_ERROR)) public void globalConfigWithLocalOverridesMethod() { } + + class Nested { + + @Sql + public void globalConfigMethod() { + } + + @Sql(config = @SqlConfig(encoding = "local", separator = "@@", commentPrefix = "#", errorMode = CONTINUE_ON_ERROR)) + public void globalConfigWithLocalOverridesMethod() { + } + } } @SqlConfig(commentPrefix = "`") @@ -363,6 +431,17 @@ class MergedSqlConfigTests { @Sql(config = @SqlConfig(commentPrefix = "#")) public void commentPrefixOverridesCommentPrefix() { } + + class Nested { + + @Sql(config = @SqlConfig(commentPrefixes = { "#", "@" })) + public void commentPrefixesOverrideCommentPrefix() { + } + + @Sql(config = @SqlConfig(commentPrefix = "#")) + public void commentPrefixOverridesCommentPrefix() { + } + } } @SqlConfig(commentPrefixes = { "`", "--" }) @@ -375,6 +454,17 @@ class MergedSqlConfigTests { @Sql(config = @SqlConfig(commentPrefix = "#")) public void commentPrefixOverridesCommentPrefixes() { } + + class Nested { + + @Sql(config = @SqlConfig(commentPrefixes = { "#", "@" })) + public void commentPrefixesOverrideCommentPrefixes() { + } + + @Sql(config = @SqlConfig(commentPrefix = "#")) + public void commentPrefixOverridesCommentPrefixes() { + } + } } } diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/RepeatableSqlAnnotationSqlScriptsChildTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/RepeatableSqlAnnotationSqlScriptsChildTests.java new file mode 100644 index 0000000000..931cd748ef --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/RepeatableSqlAnnotationSqlScriptsChildTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2020 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.Inherited; +import java.lang.annotation.Repeatable; + +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; + +/** + * Subclass of {@link RepeatableSqlAnnotationSqlScriptsParentTests} which verifies + * that {@link Repeatable} {@link Sql @Sql} annotations are not + * {@linkplain Inherited @Inherited} from a superclass if the subclass has local + * {@code @Sql} declarations. + * + * @author Sam Brannen + * @since 5.3 + */ +@Sql("schema.sql") +@Sql("data-add-catbert.sql") +class RepeatableSqlAnnotationSqlScriptsChildTests extends RepeatableSqlAnnotationSqlScriptsParentTests { + + @Test + @Order(1) + @Override + void classLevelScripts() { + // Should not find Dilbert, since local @Sql declarations shadow @Sql + // declarations on a superclass. This is due to the fact that we use + // "get" semantics instead of "find" semantics when searching for @Sql. + assertUsers("Catbert"); + } + +} 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/RepeatableSqlAnnotationSqlScriptsParentTests.java similarity index 87% rename from spring-test/src/test/java/org/springframework/test/context/jdbc/RepeatableSqlAnnotationSqlScriptsTests.java rename to spring-test/src/test/java/org/springframework/test/context/jdbc/RepeatableSqlAnnotationSqlScriptsParentTests.java index 2b3e9a52bb..d91aa638a4 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/RepeatableSqlAnnotationSqlScriptsParentTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -38,12 +38,12 @@ import org.springframework.test.context.ContextConfiguration; @Sql("schema.sql") @Sql("data.sql") @DirtiesContext -class RepeatableSqlAnnotationSqlScriptsTests extends AbstractTransactionalTests { +class RepeatableSqlAnnotationSqlScriptsParentTests extends AbstractTransactionalTests { @Test @Order(1) void classLevelScripts() { - assertNumUsers(1); + assertUsers("Dilbert"); } @Test @@ -51,9 +51,9 @@ class RepeatableSqlAnnotationSqlScriptsTests extends AbstractTransactionalTests @Sql("schema.sql") @Sql("data.sql") @Sql("data-add-dogbert.sql") - @Order(1) + @Order(2) void methodLevelScripts() { - assertNumUsers(2); + assertUsers("Dilbert", "Dogbert"); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/merging/AbstractSqlMergeModeTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/merging/AbstractSqlMergeModeTests.java index bed80c8ced..724e041136 100644 --- a/spring-test/src/test/java/org/springframework/test/context/jdbc/merging/AbstractSqlMergeModeTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/merging/AbstractSqlMergeModeTests.java @@ -30,6 +30,6 @@ import org.springframework.test.context.jdbc.SqlMergeMode; */ @ContextConfiguration(classes = EmptyDatabaseConfig.class) @DirtiesContext -abstract class AbstractSqlMergeModeTests extends AbstractTransactionalTests { +public abstract class AbstractSqlMergeModeTests extends AbstractTransactionalTests { } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/SqlScriptNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/SqlScriptNestedTests.java index 2dc297f2e1..37fcadf372 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/SqlScriptNestedTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/SqlScriptNestedTests.java @@ -23,8 +23,13 @@ import org.junit.jupiter.api.TestInstance.Lifecycle; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.NestedTestConfiguration; +import org.springframework.test.context.NestedTestConfiguration.EnclosingConfiguration; import org.springframework.test.context.jdbc.PopulatedSchemaDatabaseConfig; import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlMergeMode; +import org.springframework.test.context.jdbc.SqlMergeMode.MergeMode; +import org.springframework.test.context.jdbc.merging.AbstractSqlMergeModeTests; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.context.transaction.AfterTransaction; @@ -33,6 +38,8 @@ import org.springframework.test.jdbc.JdbcTestUtils; import org.springframework.transaction.annotation.Transactional; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.context.jdbc.SqlMergeMode.MergeMode.MERGE; +import static org.springframework.test.context.jdbc.SqlMergeMode.MergeMode.OVERRIDE; /** * Integration tests that verify support for {@link Nested @Nested} test classes in @@ -69,9 +76,6 @@ class SqlScriptNestedTests { @Nested class NestedTests { - @Autowired - JdbcTemplate jdbcTemplate; - @BeforeTransaction @AfterTransaction void checkInitialDatabaseState() { @@ -83,9 +87,72 @@ class SqlScriptNestedTests { void nestedSqlScripts() { assertThat(countRowsInTable("user")).isEqualTo(1); } + } - private int countRowsInTable(String tableName) { - return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName); + @Nested + @NestedTestConfiguration(EnclosingConfiguration.OVERRIDE) + @Sql({ + "/org/springframework/test/context/jdbc/recreate-schema.sql", + "/org/springframework/test/context/jdbc/data-add-catbert.sql" + }) + class NestedSqlMergeModeTests extends AbstractSqlMergeModeTests { + + @Nested + @NestedTestConfiguration(EnclosingConfiguration.INHERIT) + @SqlMergeMode(MergeMode.MERGE) + class NestedClassLevelMergeSqlMergeModeTests { + + @Test + void classLevelScripts() { + assertUsers("Catbert"); + } + + @Test + @Sql("/org/springframework/test/context/jdbc/data-add-dogbert.sql") + void merged() { + assertUsers("Catbert", "Dogbert"); + } + + @Test + @Sql({ + "/org/springframework/test/context/jdbc/recreate-schema.sql", + "/org/springframework/test/context/jdbc/data.sql", + "/org/springframework/test/context/jdbc/data-add-dogbert.sql", + "/org/springframework/test/context/jdbc/data-add-catbert.sql" + }) + @SqlMergeMode(MergeMode.OVERRIDE) + void overridden() { + assertUsers("Dilbert", "Dogbert", "Catbert"); + } + } + + @Nested + @NestedTestConfiguration(EnclosingConfiguration.INHERIT) + @SqlMergeMode(OVERRIDE) + class ClassLevelOverrideSqlMergeModeTests { + + @Test + void classLevelScripts() { + assertUsers("Catbert"); + } + + @Test + @Sql("/org/springframework/test/context/jdbc/data-add-dogbert.sql") + @SqlMergeMode(MERGE) + void merged() { + assertUsers("Catbert", "Dogbert"); + } + + @Test + @Sql({ + "/org/springframework/test/context/jdbc/recreate-schema.sql", + "/org/springframework/test/context/jdbc/data.sql", + "/org/springframework/test/context/jdbc/data-add-dogbert.sql", + "/org/springframework/test/context/jdbc/data-add-catbert.sql" + }) + void overridden() { + assertUsers("Dilbert", "Dogbert", "Catbert"); + } } } diff --git a/src/docs/asciidoc/testing.adoc b/src/docs/asciidoc/testing.adoc index 69d78339bd..4c53b749bc 100644 --- a/src/docs/asciidoc/testing.adoc +++ b/src/docs/asciidoc/testing.adoc @@ -1882,6 +1882,9 @@ following annotations. * <> * <> * <> +* <> +* <> +* <> * <> NOTE: The use of `@NestedTestConfiguration` typically only makes sense in conjunction