Skip to content

Commit 89571ea

Browse files
committed
Introduce @SqlMergeMode for configuring @SQL annotation merging
Closes gh-1835
1 parent ab88762 commit 89571ea

File tree

7 files changed

+258
-127
lines changed

7 files changed

+258
-127
lines changed

spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java

Lines changed: 3 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131
* SQL {@link #scripts} and {@link #statements} to be executed against a given
3232
* database during integration tests.
3333
*
34-
* <p>Method-level declarations override class-level declarations by default.
35-
* This behavior can be adjusted by setting the {@link #mergeMode}.
34+
* <p>Method-level declarations override class-level declarations by default,
35+
* but this behavior can be configured via {@link SqlMergeMode @SqlMergeMode}.
3636
*
3737
* <p>Script execution is performed by the {@link SqlScriptsTestExecutionListener},
3838
* which is enabled by default.
@@ -54,9 +54,9 @@
5454
* <em>composed annotations</em> with attribute overrides.
5555
*
5656
* @author Sam Brannen
57-
* @author Dmitry Semukhin
5857
* @since 4.1
5958
* @see SqlConfig
59+
* @see SqlMergeMode
6060
* @see SqlGroup
6161
* @see SqlScriptsTestExecutionListener
6262
* @see org.springframework.transaction.annotation.Transactional
@@ -139,16 +139,6 @@
139139
*/
140140
ExecutionPhase executionPhase() default ExecutionPhase.BEFORE_TEST_METHOD;
141141

142-
/**
143-
* Indicates whether this {@code @Sql} annotation should be merged with
144-
* class-level {@code @Sql} annotations or override them.
145-
* <p>The merge mode is ignored if declared in a class-level {@code @Sql}
146-
* annotation.
147-
* <p>Defaults to {@link MergeMode#OVERRIDE OVERRIDE} for backwards compatibility.
148-
* @since 5.2
149-
*/
150-
MergeMode mergeMode() default MergeMode.OVERRIDE;
151-
152142
/**
153143
* Local configuration for the SQL scripts and statements declared within
154144
* this {@code @Sql} annotation.
@@ -177,24 +167,4 @@ enum ExecutionPhase {
177167
AFTER_TEST_METHOD
178168
}
179169

180-
/**
181-
* Enumeration of <em>modes</em> that dictate whether method-level {@code @Sql}
182-
* declarations are merged with class-level {@code @Sql} declarations.
183-
* @since 5.2
184-
*/
185-
enum MergeMode {
186-
187-
/**
188-
* Indicates that method-level {@code @Sql} declarations should override
189-
* class-level {@code @Sql} declarations.
190-
*/
191-
OVERRIDE,
192-
193-
/**
194-
* Indicates that method-level {@code @Sql} declarations should be merged
195-
* with class-level {@code @Sql} declarations.
196-
*/
197-
MERGE
198-
}
199-
200170
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright 2002-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.context.jdbc;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Inherited;
22+
import java.lang.annotation.Retention;
23+
import java.lang.annotation.RetentionPolicy;
24+
import java.lang.annotation.Target;
25+
26+
/**
27+
* {@code @SqlMergeMode} is used to annotate a test class or test method to
28+
* configure whether method-level {@code @Sql} declarations are merged with
29+
* class-level {@code @Sql} declarations.
30+
*
31+
* <p>A method-level {@code @SqlMergeMode} declaration overrides a class-level
32+
* declaration.
33+
*
34+
* <p>If {@code @SqlMergeMode} is not declared on a test class or test method,
35+
* {@link MergeMode#OVERRIDE} will be used by default.
36+
*
37+
* <p>This annotation may be used as a <em>meta-annotation</em> to create custom
38+
* <em>composed annotations</em> with attribute overrides.
39+
*
40+
* @author Sam Brannen
41+
* @author Dmitry Semukhin
42+
* @since 5.2
43+
* @see Sql
44+
* @see MergeMode#MERGE
45+
* @see MergeMode#OVERRIDE
46+
*/
47+
@Target({ElementType.TYPE, ElementType.METHOD})
48+
@Retention(RetentionPolicy.RUNTIME)
49+
@Documented
50+
@Inherited
51+
public @interface SqlMergeMode {
52+
53+
/**
54+
* Indicates whether method-level {@code @Sql} annotations should be merged
55+
* with class-level {@code @Sql} annotations or override them.
56+
*/
57+
MergeMode value();
58+
59+
60+
/**
61+
* Enumeration of <em>modes</em> that dictate whether method-level {@code @Sql}
62+
* declarations are merged with class-level {@code @Sql} declarations.
63+
*/
64+
enum MergeMode {
65+
66+
/**
67+
* Indicates that method-level {@code @Sql} declarations should be merged
68+
* with class-level {@code @Sql} declarations, with class-level SQL
69+
* scripts and statements executed before method-level scripts and
70+
* statements.
71+
*/
72+
MERGE,
73+
74+
/**
75+
* Indicates that method-level {@code @Sql} declarations should override
76+
* class-level {@code @Sql} declarations.
77+
*/
78+
OVERRIDE
79+
80+
}
81+
82+
}

spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import java.lang.reflect.Method;
2121
import java.util.List;
2222
import java.util.Set;
23-
import java.util.stream.Collectors;
2423
import javax.sql.DataSource;
2524

2625
import org.apache.commons.logging.Log;
@@ -38,6 +37,7 @@
3837
import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
3938
import org.springframework.test.context.jdbc.SqlConfig.ErrorMode;
4039
import org.springframework.test.context.jdbc.SqlConfig.TransactionMode;
40+
import org.springframework.test.context.jdbc.SqlMergeMode.MergeMode;
4141
import org.springframework.test.context.support.AbstractTestExecutionListener;
4242
import org.springframework.test.context.transaction.TestContextTransactionUtils;
4343
import org.springframework.test.context.util.TestContextResourceUtils;
@@ -130,36 +130,57 @@ public void afterTestMethod(TestContext testContext) {
130130
* {@link TestContext} and {@link ExecutionPhase}.
131131
*/
132132
private void executeSqlScripts(TestContext testContext, ExecutionPhase executionPhase) {
133-
Set<Sql> methodLevelSqls = getSqlAnnotationsFor(testContext.getTestMethod());
134-
List<Sql> methodLevelOverrides = methodLevelSqls.stream()
135-
.filter(s -> s.executionPhase() == executionPhase)
136-
.filter(s -> s.mergeMode() == Sql.MergeMode.OVERRIDE)
137-
.collect(Collectors.toList());
138-
if (methodLevelOverrides.isEmpty()) {
139-
executeScripts(getSqlAnnotationsFor(testContext.getTestClass()), testContext, executionPhase, true);
140-
executeScripts(methodLevelSqls, testContext, executionPhase, false);
141-
} else {
142-
executeScripts(methodLevelOverrides, testContext, executionPhase, false);
133+
Method testMethod = testContext.getTestMethod();
134+
Class<?> testClass = testContext.getTestClass();
135+
136+
if (mergeSqlAnnotations(testContext)) {
137+
executeSqlScripts(getSqlAnnotationsFor(testClass), testContext, executionPhase, true);
138+
executeSqlScripts(getSqlAnnotationsFor(testMethod), testContext, executionPhase, false);
139+
}
140+
else {
141+
Set<Sql> methodLevelSqlAnnotations = getSqlAnnotationsFor(testMethod);
142+
if (!methodLevelSqlAnnotations.isEmpty()) {
143+
executeSqlScripts(methodLevelSqlAnnotations, testContext, executionPhase, false);
144+
}
145+
else {
146+
executeSqlScripts(getSqlAnnotationsFor(testClass), testContext, executionPhase, true);
147+
}
148+
}
149+
}
150+
151+
/**
152+
* Determine if method-level {@code @Sql} annotations should be merged with
153+
* class-level {@code @Sql} annotations.
154+
*/
155+
private boolean mergeSqlAnnotations(TestContext testContext) {
156+
SqlMergeMode sqlMergeMode = getSqlMergeModeFor(testContext.getTestMethod());
157+
if (sqlMergeMode == null) {
158+
sqlMergeMode = getSqlMergeModeFor(testContext.getTestClass());
143159
}
160+
return (sqlMergeMode != null && sqlMergeMode.value() == MergeMode.MERGE);
161+
}
162+
163+
/**
164+
* Get the {@code @SqlMergeMode} annotation declared on the supplied {@code element}.
165+
*/
166+
private SqlMergeMode getSqlMergeModeFor(AnnotatedElement element) {
167+
return AnnotatedElementUtils.findMergedAnnotation(element, SqlMergeMode.class);
144168
}
145169

146170
/**
147-
* Get the {@link Sql @Sql} annotations declared on the supplied
148-
* {@link AnnotatedElement}.
171+
* Get the {@code @Sql} annotations declared on the supplied {@code element}.
149172
*/
150-
private Set<Sql> getSqlAnnotationsFor(AnnotatedElement annotatedElement) {
151-
return AnnotatedElementUtils.getMergedRepeatableAnnotations(annotatedElement, Sql.class, SqlGroup.class);
173+
private Set<Sql> getSqlAnnotationsFor(AnnotatedElement element) {
174+
return AnnotatedElementUtils.getMergedRepeatableAnnotations(element, Sql.class, SqlGroup.class);
152175
}
153176

154177
/**
155178
* Execute SQL scripts for the supplied {@link Sql @Sql} annotations.
156179
*/
157-
private void executeScripts(
158-
Iterable<Sql> scripts, TestContext testContext, ExecutionPhase executionPhase, boolean classLevel) {
180+
private void executeSqlScripts(
181+
Set<Sql> sqlAnnotations, TestContext testContext, ExecutionPhase executionPhase, boolean classLevel) {
159182

160-
for (Sql sql : scripts) {
161-
executeSqlScripts(sql, executionPhase, testContext, classLevel);
162-
}
183+
sqlAnnotations.forEach(sql -> executeSqlScripts(sql, executionPhase, testContext, classLevel));
163184
}
164185

165186
/**
@@ -196,7 +217,7 @@ private void executeSqlScripts(
196217
}
197218
}
198219

199-
ResourceDatabasePopulator populator = configurePopulator(mergedSqlConfig);
220+
ResourceDatabasePopulator populator = createDatabasePopulator(mergedSqlConfig);
200221
populator.setScripts(scriptResources.toArray(new Resource[0]));
201222
if (logger.isDebugEnabled()) {
202223
logger.debug("Executing SQL scripts: " + ObjectUtils.nullSafeToString(scriptResources));
@@ -242,7 +263,7 @@ private void executeSqlScripts(
242263
}
243264

244265
@NonNull
245-
private ResourceDatabasePopulator configurePopulator(MergedSqlConfig mergedSqlConfig) {
266+
private ResourceDatabasePopulator createDatabasePopulator(MergedSqlConfig mergedSqlConfig) {
246267
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
247268
populator.setSqlScriptEncoding(mergedSqlConfig.getEncoding());
248269
populator.setSeparator(mergedSqlConfig.getSeparator());

spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodOverrideTests.java

Lines changed: 0 additions & 54 deletions
This file was deleted.

spring-test/src/test/java/org/springframework/test/context/jdbc/SqlMethodMergeTests.java renamed to spring-test/src/test/java/org/springframework/test/context/jdbc/merging/AbstractSqlMergeModeTests.java

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,38 +14,32 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.test.context.jdbc;
17+
package org.springframework.test.context.jdbc.merging;
1818

19-
import org.junit.Test;
19+
import java.util.List;
2020

2121
import org.springframework.test.annotation.DirtiesContext;
2222
import org.springframework.test.context.ContextConfiguration;
23+
import org.springframework.test.context.jdbc.EmptyDatabaseConfig;
24+
import org.springframework.test.context.jdbc.SqlMergeMode;
2325
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
2426

25-
import static org.junit.Assert.assertEquals;
26-
import static org.springframework.test.context.jdbc.Sql.MergeMode.MERGE;
27+
import static org.assertj.core.api.Assertions.assertThat;
28+
import static org.springframework.test.annotation.DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD;
2729

2830
/**
29-
* Transactional integration tests for {@link Sql @Sql} that verify proper
30-
* merging support for class-level and method-level declarations.
31+
* Abstract base class for tests involving {@link SqlMergeMode @SqlMergeMode}.
3132
*
32-
* @author Dmitry Semukhin
3333
* @author Sam Brannen
3434
* @since 5.2
3535
*/
3636
@ContextConfiguration(classes = EmptyDatabaseConfig.class)
37-
@Sql({ "schema.sql", "data-add-catbert.sql" })
38-
@DirtiesContext
39-
public class SqlMethodMergeTests extends AbstractTransactionalJUnit4SpringContextTests {
37+
@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD)
38+
abstract class AbstractSqlMergeModeTests extends AbstractTransactionalJUnit4SpringContextTests {
4039

41-
@Test
42-
@Sql(scripts = "data-add-dogbert.sql", mergeMode = MERGE)
43-
public void testMerge() {
44-
assertNumUsers(2);
45-
}
46-
47-
protected void assertNumUsers(int expected) {
48-
assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user"));
40+
protected void assertUsers(String... expectedUsers) {
41+
List<String> actualUsers = super.jdbcTemplate.queryForList("select name from user", String.class);
42+
assertThat(actualUsers).containsExactlyInAnyOrder(expectedUsers);
4943
}
5044

5145
}

0 commit comments

Comments
 (0)