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
This commit is contained in:
Sam Brannen 2020-10-22 17:01:32 +02:00
parent 95110d8257
commit b8b854db8c
10 changed files with 376 additions and 97 deletions

View File

@ -78,6 +78,9 @@ import org.springframework.lang.Nullable;
* <li>{@link org.springframework.transaction.annotation.Transactional @Transactional}</li>
* <li>{@link org.springframework.test.annotation.Rollback @Rollback}</li>
* <li>{@link org.springframework.test.annotation.Commit @Commit}</li>
* <li>{@link org.springframework.test.context.jdbc.Sql @Sql}</li>
* <li>{@link org.springframework.test.context.jdbc.SqlConfig @SqlConfig}</li>
* <li>{@link org.springframework.test.context.jdbc.SqlMergeMode @SqlMergeMode}</li>
* <li>{@link TestConstructor @TestConstructor}</li>
* </ul>
*

View File

@ -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);

View File

@ -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<Sql> 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<Sql> getSqlAnnotationsFor(Class<?> clazz) {
return MetaAnnotationUtils.getMergedRepeatableAnnotations(clazz, Sql.class);
}
/**
* Get the {@code @Sql} annotations declared on the supplied method.
*/
private Set<Sql> getSqlAnnotationsFor(Method method) {
return AnnotatedElementUtils.getMergedRepeatableAnnotations(method, Sql.class, SqlGroup.class);
}
/**

View File

@ -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.
* <p>{@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 extends Annotation> T findMergedAnnotation(Class<?> clazz, Class<T> annotationType) {
@ -106,6 +111,53 @@ public abstract class MetaAnnotationUtils {
return (descriptor != null ? descriptor.synthesizeAnnotation() : null);
}
/**
* Get all <em>repeatable annotations</em> of the specified {@code annotationType}
* within the annotation hierarchy <em>above</em> the supplied class; and for
* each annotation found, merge that annotation's attributes with <em>matching</em>
* attributes from annotations in lower levels of the annotation hierarchy and
* synthesize the results back into an annotation of the specified {@code annotationType}.
* <p>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.
* <p>The container type that holds the repeatable annotations will be looked up
* via {@link java.lang.annotation.Repeatable}.
* <p>{@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 <T extends Annotation> Set<T> getMergedRepeatableAnnotations(
Class<?> clazz, Class<T> annotationType) {
// Present (via @Inherited semantics), directly present, or meta-present?
Set<T> 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

View File

@ -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() {
}
}
}
}

View File

@ -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");
}
}

View File

@ -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");
}
}

View File

@ -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 {
}

View File

@ -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");
}
}
}

View File

@ -1882,6 +1882,9 @@ following annotations.
* <<testcontext-tx,`@Transactional`>>
* <<spring-testing-annotation-commit>>
* <<spring-testing-annotation-rollback>>
* <<spring-testing-annotation-sql>>
* <<spring-testing-annotation-sqlconfig>>
* <<spring-testing-annotation-sqlmergemode>>
* <<integration-testing-annotations-testconstructor>>
NOTE: The use of `@NestedTestConfiguration` typically only makes sense in conjunction