Skip to content

Commit 0afcb4d

Browse files
committed
Revise "Introduce class-level execution phases for @⁠Sql"
This commit revises the previous commit as follows. - Remove hasTestMethod() from TestContext and instead introduce a new variant of createDelegatingTransactionAttribute() in TestContextTransactionUtils which accepts a boolean `includeMethodName` flag. - Add missing @⁠TestMethodOrder declaration to AfterTestClassSqlScriptsTests to ensure that the test methods are always executed in the required order. - Polish Javadoc and add missing @⁠since tags. Closes gh-27285
1 parent 5aa2d05 commit 0afcb4d

File tree

8 files changed

+96
-89
lines changed

8 files changed

+96
-89
lines changed

spring-test/src/main/java/org/springframework/test/context/TestContext.java

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
* override {@link #setMethodInvoker(MethodInvoker)} and {@link #getMethodInvoker()}.
4343
*
4444
* @author Sam Brannen
45-
* @author Andreas Ahlenstorf
4645
* @since 2.5
4746
* @see TestContextManager
4847
* @see TestExecutionListener
@@ -111,25 +110,6 @@ default void publishEvent(Function<TestContext, ? extends ApplicationEvent> even
111110
*/
112111
Object getTestInstance();
113112

114-
/**
115-
* Tests whether a test method is part of this test context. Returns
116-
* {@code true} if this context has a current test method, {@code false}
117-
* otherwise.
118-
*
119-
* <p>The default implementation of this method always returns {@code false}.
120-
* Custom {@code TestContext} implementations are therefore highly encouraged
121-
* to override this method with a more meaningful implementation. Note that
122-
* the standard {@code TestContext} implementation in Spring overrides this
123-
* method appropriately.
124-
* @return {@code true} if the test execution has already entered a test
125-
* method
126-
* @since 6.1
127-
* @see #getTestMethod()
128-
*/
129-
default boolean hasTestMethod() {
130-
return false;
131-
}
132-
133113
/**
134114
* Get the current {@linkplain Method test method} for this test context.
135115
* <p>Note: this is a mutable property.

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

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,12 @@
3333
*
3434
* <p>Method-level declarations override class-level declarations by default,
3535
* but this behavior can be configured via {@link SqlMergeMode @SqlMergeMode}.
36-
* However, this does not apply to class-level declarations that use
37-
* {@link ExecutionPhase#BEFORE_TEST_CLASS} or
38-
* {@link ExecutionPhase#AFTER_TEST_CLASS}. Such declarations are retained and
39-
* scripts and statements are executed once per class in addition to any
40-
* method-level annotations.
36+
* However, this does not apply to class-level declarations configured for the
37+
* {@link ExecutionPhase#BEFORE_TEST_CLASS BEFORE_TEST_CLASS} or
38+
* {@link ExecutionPhase#AFTER_TEST_CLASS AFTER_TEST_CLASS} execution phase. Such
39+
* declarations cannot be overridden, and the corresponding scripts and statements
40+
* will be executed once per class in addition to any method-level scripts and
41+
* statements.
4142
*
4243
* <p>Script execution is performed by the {@link SqlScriptsTestExecutionListener},
4344
* which is enabled by default.
@@ -169,13 +170,15 @@ enum ExecutionPhase {
169170

170171
/**
171172
* The configured SQL scripts and statements will be executed
172-
* once <em>before</em> any test method is run.
173+
* once per test class <em>before</em> any test method is run.
174+
* @since 6.1
173175
*/
174176
BEFORE_TEST_CLASS,
175177

176178
/**
177179
* The configured SQL scripts and statements will be executed
178-
* once <em>after</em> any test method is run.
180+
* once per test class <em>after</em> all test methods have run.
181+
* @since 6.1
179182
*/
180183
AFTER_TEST_CLASS,
181184

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

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@
6868
* configured via the {@link Sql @Sql} annotation.
6969
*
7070
* <p>Class-level annotations that are constrained to a class-level execution
71-
* phase ({@link ExecutionPhase#BEFORE_TEST_CLASS} or
72-
* {@link ExecutionPhase#AFTER_TEST_CLASS}) will be run
71+
* phase ({@link ExecutionPhase#BEFORE_TEST_CLASS BEFORE_TEST_CLASS} or
72+
* {@link ExecutionPhase#AFTER_TEST_CLASS AFTER_TEST_CLASS}) will be run
7373
* {@linkplain #beforeTestClass(TestContext) once before all test methods} or
7474
* {@linkplain #afterTestMethod(TestContext) once after all test methods},
7575
* respectively. All other scripts and inlined statements will be executed
@@ -138,20 +138,22 @@ public final int getOrder() {
138138
* Execute SQL scripts configured via {@link Sql @Sql} for the supplied
139139
* {@link TestContext} once per test class <em>before</em> any test method
140140
* is run.
141+
* @since 6.1
141142
*/
142143
@Override
143144
public void beforeTestClass(TestContext testContext) throws Exception {
144-
executeBeforeOrAfterClassSqlScripts(testContext, ExecutionPhase.BEFORE_TEST_CLASS);
145+
executeClassLevelSqlScripts(testContext, ExecutionPhase.BEFORE_TEST_CLASS);
145146
}
146147

147148
/**
148149
* Execute SQL scripts configured via {@link Sql @Sql} for the supplied
149150
* {@link TestContext} once per test class <em>after</em> all test methods
150151
* have been run.
152+
* @since 6.1
151153
*/
152154
@Override
153155
public void afterTestClass(TestContext testContext) throws Exception {
154-
executeBeforeOrAfterClassSqlScripts(testContext, ExecutionPhase.AFTER_TEST_CLASS);
156+
executeClassLevelSqlScripts(testContext, ExecutionPhase.AFTER_TEST_CLASS);
155157
}
156158

157159
/**
@@ -189,11 +191,12 @@ public void processAheadOfTime(RuntimeHints runtimeHints, Class<?> testClass, Cl
189191

190192
/**
191193
* Execute class-level SQL scripts configured via {@link Sql @Sql} for the
192-
* supplied {@link TestContext} and the execution phases
193-
* {@link ExecutionPhase#BEFORE_TEST_CLASS} and
194-
* {@link ExecutionPhase#AFTER_TEST_CLASS}.
194+
* supplied {@link TestContext} and the supplied
195+
* {@link ExecutionPhase#BEFORE_TEST_CLASS BEFORE_TEST_CLASS} or
196+
* {@link ExecutionPhase#AFTER_TEST_CLASS AFTER_TEST_CLASS} execution phase.
197+
* @since 6.1
195198
*/
196-
private void executeBeforeOrAfterClassSqlScripts(TestContext testContext, ExecutionPhase executionPhase) {
199+
private void executeClassLevelSqlScripts(TestContext testContext, ExecutionPhase executionPhase) {
197200
Class<?> testClass = testContext.getTestClass();
198201
executeSqlScripts(getSqlAnnotationsFor(testClass), testContext, executionPhase, true);
199202
}
@@ -286,7 +289,7 @@ private void executeSqlScripts(
286289
Sql sql, ExecutionPhase executionPhase, TestContext testContext, boolean classLevel) {
287290

288291
Assert.isTrue(classLevel || isValidMethodLevelPhase(sql.executionPhase()),
289-
() -> "%s cannot be used on methods".formatted(sql.executionPhase()));
292+
() -> "@SQL execution phase %s cannot be used on methods".formatted(sql.executionPhase()));
290293

291294
if (executionPhase != sql.executionPhase()) {
292295
return;
@@ -302,10 +305,8 @@ else if (logger.isDebugEnabled()) {
302305
.formatted(executionPhase, testContext.getTestClass().getName()));
303306
}
304307

305-
Method testMethod = null;
306-
if (testContext.hasTestMethod()) {
307-
testMethod = testContext.getTestMethod();
308-
}
308+
boolean methodLevel = !classLevel;
309+
Method testMethod = (methodLevel ? testContext.getTestMethod() : null);
309310

310311
String[] scripts = getScripts(sql, testContext.getTestClass(), testMethod, classLevel);
311312
List<Resource> scriptResources = TestContextResourceUtils.convertToResourceList(
@@ -357,7 +358,7 @@ else if (logger.isDebugEnabled()) {
357358
int propagation = (newTxRequired ? TransactionDefinition.PROPAGATION_REQUIRES_NEW :
358359
TransactionDefinition.PROPAGATION_REQUIRED);
359360
TransactionAttribute txAttr = TestContextTransactionUtils.createDelegatingTransactionAttribute(
360-
testContext, new DefaultTransactionAttribute(propagation));
361+
testContext, new DefaultTransactionAttribute(propagation), methodLevel);
361362
new TransactionTemplate(txMgr, txAttr).executeWithoutResult(s -> populator.execute(finalDataSource));
362363
}
363364
}
@@ -458,7 +459,8 @@ private void registerClasspathResources(String[] paths, RuntimeHints runtimeHint
458459

459460
private static boolean isValidMethodLevelPhase(ExecutionPhase executionPhase) {
460461
// Class-level phases cannot be used on methods.
461-
return executionPhase == ExecutionPhase.BEFORE_TEST_METHOD ||
462-
executionPhase == ExecutionPhase.AFTER_TEST_METHOD;
462+
return (executionPhase == ExecutionPhase.BEFORE_TEST_METHOD ||
463+
executionPhase == ExecutionPhase.AFTER_TEST_METHOD);
463464
}
465+
464466
}

spring-test/src/main/java/org/springframework/test/context/support/DefaultTestContext.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
* @author Sam Brannen
4242
* @author Juergen Hoeller
4343
* @author Rob Harrop
44-
* @author Andreas Ahlenstorf
4544
* @since 4.0
4645
*/
4746
@SuppressWarnings("serial")
@@ -167,11 +166,6 @@ public final Object getTestInstance() {
167166
return testInstance;
168167
}
169168

170-
@Override
171-
public boolean hasTestMethod() {
172-
return this.testMethod != null;
173-
}
174-
175169
@Override
176170
public final Method getTestMethod() {
177171
Method testMethod = this.testMethod;

spring-test/src/main/java/org/springframework/test/context/transaction/TestContextTransactionUtils.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -228,18 +228,35 @@ private static void logBeansException(TestContext testContext, BeansException ex
228228
/**
229229
* Create a delegating {@link TransactionAttribute} for the supplied target
230230
* {@link TransactionAttribute} and {@link TestContext}, using the names of
231-
* the test class and test method (if available) to build the name of the
232-
* transaction.
231+
* the test class and test method to build the name of the transaction.
233232
* @param testContext the {@code TestContext} upon which to base the name
234233
* @param targetAttribute the {@code TransactionAttribute} to delegate to
235234
* @return the delegating {@code TransactionAttribute}
236235
*/
237236
public static TransactionAttribute createDelegatingTransactionAttribute(
238237
TestContext testContext, TransactionAttribute targetAttribute) {
239238

239+
return createDelegatingTransactionAttribute(testContext, targetAttribute, true);
240+
}
241+
242+
/**
243+
* Create a delegating {@link TransactionAttribute} for the supplied target
244+
* {@link TransactionAttribute} and {@link TestContext}, using the names of
245+
* the test class and test method (if requested) to build the name of the
246+
* transaction.
247+
* @param testContext the {@code TestContext} upon which to base the name
248+
* @param targetAttribute the {@code TransactionAttribute} to delegate to
249+
* @param includeMethodName {@code true} if the test method's name should be
250+
* included in the name of the transaction
251+
* @return the delegating {@code TransactionAttribute}
252+
* @since 6.1
253+
*/
254+
public static TransactionAttribute createDelegatingTransactionAttribute(
255+
TestContext testContext, TransactionAttribute targetAttribute, boolean includeMethodName) {
256+
240257
Assert.notNull(testContext, "TestContext must not be null");
241258
Assert.notNull(targetAttribute, "Target TransactionAttribute must not be null");
242-
return new TestContextTransactionAttribute(targetAttribute, testContext);
259+
return new TestContextTransactionAttribute(targetAttribute, testContext, includeMethodName);
243260
}
244261

245262

@@ -248,10 +265,12 @@ private static class TestContextTransactionAttribute extends DelegatingTransacti
248265

249266
private final String name;
250267

251-
public TestContextTransactionAttribute(TransactionAttribute targetAttribute, TestContext testContext) {
268+
public TestContextTransactionAttribute(
269+
TransactionAttribute targetAttribute, TestContext testContext, boolean includeMethodName) {
270+
252271
super(targetAttribute);
253272

254-
if (testContext.hasTestMethod()) {
273+
if (includeMethodName) {
255274
this.name = ClassUtils.getQualifiedMethodName(testContext.getTestMethod(), testContext.getTestClass());
256275
}
257276
else {

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

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,70 +18,76 @@
1818

1919
import javax.sql.DataSource;
2020

21+
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
2122
import org.junit.jupiter.api.Order;
2223
import org.junit.jupiter.api.Test;
24+
import org.junit.jupiter.api.TestMethodOrder;
2325

24-
import org.springframework.core.Ordered;
2526
import org.springframework.jdbc.BadSqlGrammarException;
2627
import org.springframework.jdbc.core.JdbcTemplate;
2728
import org.springframework.test.annotation.Commit;
2829
import org.springframework.test.annotation.DirtiesContext;
2930
import org.springframework.test.context.TestContext;
30-
import org.springframework.test.context.TestExecutionListener;
3131
import org.springframework.test.context.TestExecutionListeners;
32+
import org.springframework.test.context.jdbc.AfterTestClassSqlScriptsTests.VerifySchemaDroppedListener;
33+
import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
3234
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
35+
import org.springframework.test.context.support.AbstractTestExecutionListener;
3336
import org.springframework.test.context.transaction.TestContextTransactionUtils;
3437

3538
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
39+
import static org.springframework.test.context.TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS;
40+
import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.AFTER_TEST_CLASS;
3641

3742
/**
38-
* Verifies that {@link Sql @Sql} with {@link Sql.ExecutionPhase#AFTER_TEST_CLASS} is run after all tests in the class
39-
* have been run.
43+
* Verifies that {@link Sql @Sql} with {@link ExecutionPhase#AFTER_TEST_CLASS}
44+
* is run after all tests in the class have been run.
4045
*
4146
* @author Andreas Ahlenstorf
47+
* @author Sam Brannen
4248
* @since 6.1
4349
*/
4450
@SpringJUnitConfig(PopulatedSchemaDatabaseConfig.class)
45-
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
46-
@Sql(value = {"drop-schema.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_CLASS)
47-
@TestExecutionListeners(
48-
value = AfterTestClassSqlScriptsTests.VerifyTestExecutionListener.class,
49-
mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS
50-
)
51+
@TestMethodOrder(OrderAnnotation.class)
52+
@DirtiesContext
53+
@Sql(scripts = "drop-schema.sql", executionPhase = AFTER_TEST_CLASS)
54+
@Commit
55+
@TestExecutionListeners(listeners = VerifySchemaDroppedListener.class, mergeMode = MERGE_WITH_DEFAULTS)
5156
class AfterTestClassSqlScriptsTests extends AbstractTransactionalTests {
5257

5358
@Test
5459
@Order(1)
55-
@Sql(scripts = "data-add-catbert.sql")
56-
@Commit
60+
@Sql("data-add-catbert.sql")
5761
void databaseHasBeenInitialized() {
5862
assertUsers("Catbert");
5963
}
6064

6165
@Test
6266
@Order(2)
63-
@Sql(scripts = "data-add-dogbert.sql")
64-
@Commit
67+
@Sql("data-add-dogbert.sql")
6568
void databaseIsNotWipedBetweenTests() {
6669
assertUsers("Catbert", "Dogbert");
6770
}
6871

69-
static class VerifyTestExecutionListener implements TestExecutionListener, Ordered {
72+
73+
static class VerifySchemaDroppedListener extends AbstractTestExecutionListener {
74+
75+
@Override
76+
public int getOrder() {
77+
// Must run before DirtiesContextTestExecutionListener. Otherwise, the
78+
// old data source will be removed and replaced with a new one.
79+
return 3001;
80+
}
7081

7182
@Override
7283
public void afterTestClass(TestContext testContext) throws Exception {
7384
DataSource dataSource = TestContextTransactionUtils.retrieveDataSource(testContext, null);
7485
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
7586

7687
assertThatExceptionOfType(BadSqlGrammarException.class)
77-
.isThrownBy(() -> jdbcTemplate.queryForList("SELECT name FROM user", String.class));
78-
}
79-
80-
@Override
81-
public int getOrder() {
82-
// Must run before DirtiesContextTestExecutionListener. Otherwise, the old data source will be removed and
83-
// replaced with a new one.
84-
return 3001;
88+
.isThrownBy(() -> jdbcTemplate.queryForList("SELECT name FROM user", String.class))
89+
.withMessageContaining("user");
8590
}
8691
}
92+
8793
}

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

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,24 @@
1919
import org.junit.jupiter.api.Test;
2020

2121
import org.springframework.test.annotation.DirtiesContext;
22+
import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
2223
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
2324

25+
import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_CLASS;
2426
import static org.springframework.test.context.jdbc.SqlMergeMode.MergeMode.MERGE;
2527
import static org.springframework.test.context.jdbc.SqlMergeMode.MergeMode.OVERRIDE;
2628

2729
/**
28-
* Verifies that {@link Sql @Sql} with {@link Sql.ExecutionPhase#BEFORE_TEST_CLASS} is run before all tests in the class
29-
* have been run.
30+
* Verifies that {@link Sql @Sql} with {@link ExecutionPhase#BEFORE_TEST_CLASS}
31+
* is run before all tests in the class have been run.
3032
*
3133
* @author Andreas Ahlenstorf
34+
* @author Sam Brannen
3235
* @since 6.1
3336
*/
34-
@SpringJUnitConfig(classes = EmptyDatabaseConfig.class)
37+
@SpringJUnitConfig(EmptyDatabaseConfig.class)
3538
@DirtiesContext
36-
@Sql(value = {"schema.sql", "data-add-catbert.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS)
39+
@Sql(scripts = {"schema.sql", "data-add-catbert.sql"}, executionPhase = BEFORE_TEST_CLASS)
3740
class BeforeTestClassSqlScriptsTests extends AbstractTransactionalTests {
3841

3942
@Test
@@ -52,7 +55,7 @@ void mergeDoesNotAffectClassLevelPhase() {
5255
@Sql({"data-add-dogbert.sql"})
5356
@SqlMergeMode(OVERRIDE)
5457
void overrideDoesNotAffectClassLevelPhase() {
55-
assertUsers("Dogbert", "Catbert");
58+
assertUsers("Catbert", "Dogbert");
5659
}
5760

5861
}

0 commit comments

Comments
 (0)