Introduce @SqlMergeMode for configuring @Sql annotation merging
Closes gh-1835
This commit is contained in:
parent
ab8876219f
commit
89571ea236
|
@ -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.
|
||||
*
|
||||
* <p>Method-level declarations override class-level declarations by default.
|
||||
* This behavior can be adjusted by setting the {@link #mergeMode}.
|
||||
* <p>Method-level declarations override class-level declarations by default,
|
||||
* but this behavior can be configured via {@link SqlMergeMode @SqlMergeMode}.
|
||||
*
|
||||
* <p>Script execution is performed by the {@link SqlScriptsTestExecutionListener},
|
||||
* which is enabled by default.
|
||||
|
@ -54,9 +54,9 @@ import org.springframework.core.annotation.AliasFor;
|
|||
* <em>composed annotations</em> 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.
|
||||
* <p>The merge mode is ignored if declared in a class-level {@code @Sql}
|
||||
* annotation.
|
||||
* <p>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 <em>modes</em> 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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.jdbc;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* {@code @SqlMergeMode} is used to annotate a test class or test method to
|
||||
* configure whether method-level {@code @Sql} declarations are merged with
|
||||
* class-level {@code @Sql} declarations.
|
||||
*
|
||||
* <p>A method-level {@code @SqlMergeMode} declaration overrides a class-level
|
||||
* declaration.
|
||||
*
|
||||
* <p>If {@code @SqlMergeMode} is not declared on a test class or test method,
|
||||
* {@link MergeMode#OVERRIDE} will be used by default.
|
||||
*
|
||||
* <p>This annotation may be used as a <em>meta-annotation</em> to create custom
|
||||
* <em>composed annotations</em> with attribute overrides.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @author Dmitry Semukhin
|
||||
* @since 5.2
|
||||
* @see Sql
|
||||
* @see MergeMode#MERGE
|
||||
* @see MergeMode#OVERRIDE
|
||||
*/
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
public @interface SqlMergeMode {
|
||||
|
||||
/**
|
||||
* Indicates whether method-level {@code @Sql} annotations should be merged
|
||||
* with class-level {@code @Sql} annotations or override them.
|
||||
*/
|
||||
MergeMode value();
|
||||
|
||||
|
||||
/**
|
||||
* Enumeration of <em>modes</em> that dictate whether method-level {@code @Sql}
|
||||
* declarations are merged with class-level {@code @Sql} declarations.
|
||||
*/
|
||||
enum MergeMode {
|
||||
|
||||
/**
|
||||
* Indicates that method-level {@code @Sql} declarations should be merged
|
||||
* with class-level {@code @Sql} declarations, with class-level SQL
|
||||
* scripts and statements executed before method-level scripts and
|
||||
* statements.
|
||||
*/
|
||||
MERGE,
|
||||
|
||||
/**
|
||||
* Indicates that method-level {@code @Sql} declarations should override
|
||||
* class-level {@code @Sql} declarations.
|
||||
*/
|
||||
OVERRIDE
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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<Sql> methodLevelSqls = getSqlAnnotationsFor(testContext.getTestMethod());
|
||||
List<Sql> 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<Sql> 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<Sql> 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<Sql> 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<Sql> scripts, TestContext testContext, ExecutionPhase executionPhase, boolean classLevel) {
|
||||
private void executeSqlScripts(
|
||||
Set<Sql> 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());
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
||||
}
|
|
@ -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<String> actualUsers = super.jdbcTemplate.queryForList("select name from user", String.class);
|
||||
assertThat(actualUsers).containsExactlyInAnyOrder(expectedUsers);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.jdbc.merging;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.test.context.jdbc.Sql;
|
||||
import org.springframework.test.context.jdbc.SqlMergeMode;
|
||||
|
||||
import static org.springframework.test.context.jdbc.SqlMergeMode.MergeMode.MERGE;
|
||||
import static org.springframework.test.context.jdbc.SqlMergeMode.MergeMode.OVERRIDE;
|
||||
|
||||
/**
|
||||
* Transactional integration tests that verify proper merging and overriding support
|
||||
* for class-level and method-level {@link Sql @Sql} declarations when
|
||||
* {@link SqlMergeMode @SqlMergeMode} is declared at the class level with
|
||||
* {@link SqlMergeMode.MergeMode#MERGE MERGE} mode.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @author Dmitry Semukhin
|
||||
* @since 5.2
|
||||
*/
|
||||
@Sql({ "../schema.sql", "../data-add-catbert.sql" })
|
||||
@SqlMergeMode(MERGE)
|
||||
public class ClassLevelMergeSqlMergeModeTests extends AbstractSqlMergeModeTests {
|
||||
|
||||
@Test
|
||||
public void classLevelScripts() {
|
||||
assertUsers("Catbert");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Sql("../data-add-dogbert.sql")
|
||||
public void merged() {
|
||||
assertUsers("Catbert", "Dogbert");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Sql({ "../schema.sql", "../data.sql", "../data-add-dogbert.sql", "../data-add-catbert.sql" })
|
||||
@SqlMergeMode(OVERRIDE)
|
||||
public void overridden() {
|
||||
assertUsers("Dilbert", "Dogbert", "Catbert");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.jdbc.merging;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.test.context.jdbc.Sql;
|
||||
import org.springframework.test.context.jdbc.SqlMergeMode;
|
||||
|
||||
import static org.springframework.test.context.jdbc.SqlMergeMode.MergeMode.MERGE;
|
||||
import static org.springframework.test.context.jdbc.SqlMergeMode.MergeMode.OVERRIDE;
|
||||
|
||||
/**
|
||||
* Transactional integration tests that verify proper merging and overriding support
|
||||
* for class-level and method-level {@link Sql @Sql} declarations when
|
||||
* {@link SqlMergeMode @SqlMergeMode} is declared at the class level with
|
||||
* {@link SqlMergeMode.MergeMode#OVERRIDE OVERRIDE} mode.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @author Dmitry Semukhin
|
||||
* @since 5.2
|
||||
*/
|
||||
@Sql({ "../schema.sql", "../data-add-catbert.sql" })
|
||||
@SqlMergeMode(OVERRIDE)
|
||||
public class ClassLevelOverrideSqlMergeModeTests extends AbstractSqlMergeModeTests {
|
||||
|
||||
@Test
|
||||
public void classLevelScripts() {
|
||||
assertUsers("Catbert");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Sql("../data-add-dogbert.sql")
|
||||
@SqlMergeMode(MERGE)
|
||||
public void merged() {
|
||||
assertUsers("Catbert", "Dogbert");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Sql({ "../schema.sql", "../data.sql", "../data-add-dogbert.sql", "../data-add-catbert.sql" })
|
||||
public void overridden() {
|
||||
assertUsers("Dilbert", "Dogbert", "Catbert");
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue