Skip to content

Commit ed83461

Browse files
committed
Support parameter injection in @[Before|After]Transaction methods
Prior to this commit, @​BeforeTransaction and @​AfterTransaction methods could not accept any arguments. This is acceptable with testing frameworks such as JUnit 4 and TestNG that do not provide support for parameter injection. However, users of JUnit Jupiter have become accustomed to being able to accept arguments in lifecycle methods annotated with JUnit's @​BeforeEach, @​AfterEach, etc. As a follow up to the previous commit (see gh-31199), this commit introduces first-class support for parameter injection in @​BeforeTransaction and @​AfterTransaction methods, as demonstrated in the following example. @​BeforeTransaction void verifyInitialDatabaseState(@Autowired DataSource dataSource) { // Use the DataSource to verify the initial DB state } Closes gh-30736
1 parent 41904d4 commit ed83461

File tree

6 files changed

+187
-22
lines changed

6 files changed

+187
-22
lines changed

framework-docs/modules/ROOT/pages/testing/testcontext-framework/support-classes.adoc

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ following features above and beyond the feature set that Spring supports for JUn
177177
TestNG:
178178

179179
* Dependency injection for test constructors, test methods, and test lifecycle callback
180-
methods. See xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-di[Dependency Injection with `SpringExtension`] for further details.
180+
methods. See xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-di[Dependency Injection with the `SpringExtension`] for further details.
181181
* Powerful support for link:https://junit.org/junit5/docs/current/user-guide/#extensions-conditions[conditional
182182
test execution] based on SpEL expressions, environment variables, system properties,
183183
and so on. See the documentation for `@EnabledIf` and `@DisabledIf` in
@@ -310,17 +310,19 @@ See the documentation for `@SpringJUnitConfig` and `@SpringJUnitWebConfig` in
310310
xref:testing/annotations/integration-junit-jupiter.adoc[Spring JUnit Jupiter Testing Annotations] for further details.
311311

312312
[[testcontext-junit-jupiter-di]]
313-
=== Dependency Injection with `SpringExtension`
313+
=== Dependency Injection with the `SpringExtension`
314314

315-
`SpringExtension` implements the
315+
The `SpringExtension` implements the
316316
link:https://junit.org/junit5/docs/current/user-guide/#extensions-parameter-resolution[`ParameterResolver`]
317317
extension API from JUnit Jupiter, which lets Spring provide dependency injection for test
318318
constructors, test methods, and test lifecycle callback methods.
319319

320-
Specifically, `SpringExtension` can inject dependencies from the test's
321-
`ApplicationContext` into test constructors and methods that are annotated with
322-
`@BeforeAll`, `@AfterAll`, `@BeforeEach`, `@AfterEach`, `@Test`, `@RepeatedTest`,
323-
`@ParameterizedTest`, and others.
320+
Specifically, the `SpringExtension` can inject dependencies from the test's
321+
`ApplicationContext` into into test constructors and methods that are annotated with
322+
Spring's `@BeforeTransaction` and `@AfterTransaction` or JUnit's `@BeforeAll`,
323+
`@AfterAll`, `@BeforeEach`, `@AfterEach`, `@Test`, `@RepeatedTest`, `@ParameterizedTest`,
324+
and others.
325+
324326

325327
[[testcontext-junit-jupiter-di-constructor]]
326328
==== Constructor Injection

framework-docs/modules/ROOT/pages/testing/testcontext-framework/tx.adoc

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,44 @@ method in a test class or any `void` default method in a test interface with one
298298
annotations, and the `TransactionalTestExecutionListener` ensures that your
299299
before-transaction method or after-transaction method runs at the appropriate time.
300300

301+
[NOTE]
302+
====
303+
Generally speaking, `@BeforeTransaction` and `@AfterTransaction` methods must not accept
304+
any arguments.
305+
306+
However, as of Spring Framework 6.1, for tests using the
307+
xref:testing/testcontext-framework/support-classes.adoc#testcontext-junit-jupiter-extension[`SpringExtension`]
308+
with JUnit Jupiter, `@BeforeTransaction` and `@AfterTransaction` methods may optionally
309+
accept arguments which will be resolved by any registered JUnit `ParameterResolver`
310+
extension such as the `SpringExtension`. This means that JUnit-specific arguments like
311+
`TestInfo` or beans from the test's `ApplicationContext` may be provided to
312+
`@BeforeTransaction` and `@AfterTransaction` methods, as demonstrated in the following
313+
example.
314+
315+
[tabs]
316+
======
317+
Java::
318+
+
319+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
320+
----
321+
@BeforeTransaction
322+
void verifyInitialDatabaseState(@Autowired DataSource dataSource) {
323+
// Use the DataSource to verify the initial state before a transaction is started
324+
}
325+
----
326+
327+
Kotlin::
328+
+
329+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
330+
----
331+
@BeforeTransaction
332+
fun verifyInitialDatabaseState(@Autowired dataSource: DataSource) {
333+
// Use the DataSource to verify the initial state before a transaction is started
334+
}
335+
----
336+
======
337+
====
338+
301339
[TIP]
302340
====
303341
Any before methods (such as methods annotated with JUnit Jupiter's `@BeforeEach`) and any

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,20 +23,28 @@
2323
import java.lang.annotation.Target;
2424

2525
/**
26-
* <p>Test annotation which indicates that the annotated {@code void} method
26+
* Test annotation which indicates that the annotated {@code void} method
2727
* should be executed <em>after</em> a transaction is ended for a test method
2828
* configured to run within a transaction via Spring's {@code @Transactional}
2929
* annotation.
3030
*
31+
* <p>Generally speaking, {@code @AfterTransaction} methods must not accept any
32+
* arguments. However, as of Spring Framework 6.1, for tests using the
33+
* {@link org.springframework.test.context.junit.jupiter.SpringExtension SpringExtension}
34+
* with JUnit Jupiter, {@code @AfterTransaction} methods may optionally accept
35+
* arguments which will be resolved by any registered JUnit
36+
* {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolver}
37+
* extension such as the {@code SpringExtension}. This means that JUnit-specific
38+
* arguments like {@link org.junit.jupiter.api.TestInfo TestInfo} or beans from
39+
* the test's {@code ApplicationContext} may be provided to {@code @AfterTransaction}
40+
* methods analogous to {@code @AfterEach} methods.
41+
*
3142
* <p>{@code @AfterTransaction} methods declared in superclasses or as interface
3243
* default methods will be executed after those of the current test class.
3344
*
3445
* <p>This annotation may be used as a <em>meta-annotation</em> to create custom
3546
* <em>composed annotations</em>.
3647
*
37-
* <p>As of Spring Framework 4.3, {@code @AfterTransaction} may also be
38-
* declared on Java 8 based interface default methods.
39-
*
4048
* @author Sam Brannen
4149
* @since 2.5
4250
* @see org.springframework.transaction.annotation.Transactional

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,20 +23,28 @@
2323
import java.lang.annotation.Target;
2424

2525
/**
26-
* <p>Test annotation which indicates that the annotated {@code void} method
26+
* Test annotation which indicates that the annotated {@code void} method
2727
* should be executed <em>before</em> a transaction is started for a test method
2828
* configured to run within a transaction via Spring's {@code @Transactional}
2929
* annotation.
3030
*
31+
* <p>Generally speaking, {@code @BeforeTransaction} methods must not accept any
32+
* arguments. However, as of Spring Framework 6.1, for tests using the
33+
* {@link org.springframework.test.context.junit.jupiter.SpringExtension SpringExtension}
34+
* with JUnit Jupiter, {@code @BeforeTransaction} methods may optionally accept
35+
* arguments which will be resolved by any registered JUnit
36+
* {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolver}
37+
* extension such as the {@code SpringExtension}. This means that JUnit-specific
38+
* arguments like {@link org.junit.jupiter.api.TestInfo TestInfo} or beans from
39+
* the test's {@code ApplicationContext} may be provided to {@code @BeforeTransaction}
40+
* methods analogous to {@code @BeforeEach} methods.
41+
*
3142
* <p>{@code @BeforeTransaction} methods declared in superclasses or as interface
3243
* default methods will be executed before those of the current test class.
3344
*
3445
* <p>This annotation may be used as a <em>meta-annotation</em> to create custom
3546
* <em>composed annotations</em>.
3647
*
37-
* <p>As of Spring Framework 4.3, {@code @BeforeTransaction} may also be
38-
* declared on Java 8 based interface default methods.
39-
*
4048
* @author Sam Brannen
4149
* @since 2.5
4250
* @see org.springframework.transaction.annotation.Transactional

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -287,8 +287,7 @@ else if (logger.isDebugEnabled()) {
287287
logger.debug("Executing @BeforeTransaction method [%s] for test class [%s]"
288288
.formatted(method, testClass.getName()));
289289
}
290-
ReflectionUtils.makeAccessible(method);
291-
method.invoke(testContext.getTestInstance());
290+
testContext.getMethodInvoker().invoke(method, testContext.getTestInstance());
292291
}
293292
}
294293
catch (InvocationTargetException ex) {
@@ -323,8 +322,7 @@ else if (logger.isDebugEnabled()) {
323322
logger.debug("Executing @AfterTransaction method [%s] for test class [%s]"
324323
.formatted(method, testClass.getName()));
325324
}
326-
ReflectionUtils.makeAccessible(method);
327-
method.invoke(testContext.getTestInstance());
325+
testContext.getMethodInvoker().invoke(method, testContext.getTestInstance());
328326
}
329327
catch (InvocationTargetException ex) {
330328
Throwable targetException = ex.getTargetException();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Copyright 2002-2023 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.junit.jupiter.transaction;
18+
19+
import javax.sql.DataSource;
20+
21+
import org.junit.jupiter.api.AfterAll;
22+
import org.junit.jupiter.api.BeforeAll;
23+
import org.junit.jupiter.api.Test;
24+
import org.junit.jupiter.api.TestInfo;
25+
26+
import org.springframework.beans.factory.annotation.Autowired;
27+
import org.springframework.context.ApplicationContext;
28+
import org.springframework.context.annotation.Bean;
29+
import org.springframework.context.annotation.Configuration;
30+
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
31+
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
32+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
33+
import org.springframework.test.context.transaction.AfterTransaction;
34+
import org.springframework.test.context.transaction.BeforeTransaction;
35+
import org.springframework.transaction.annotation.Transactional;
36+
37+
import static org.assertj.core.api.Assertions.assertThat;
38+
import static org.springframework.test.transaction.TransactionAssert.assertThatTransaction;
39+
40+
/**
41+
* JUnit Jupiter based integration tests which verify support for parameter
42+
* injection in {@link BeforeTransaction @BeforeTransaction} and
43+
* {@link AfterTransaction @AfterTransaction} lifecycle methods.
44+
*
45+
* @author Sam Brannen
46+
* @since 6.1
47+
*/
48+
@SpringJUnitConfig
49+
class TransactionLifecycleMethodParameterInjectionTests {
50+
51+
static boolean beforeTransactionInvoked = false;
52+
static boolean afterTransactionInvoked = false;
53+
54+
55+
@BeforeAll
56+
static void checkInitialFlagState() {
57+
assertThat(beforeTransactionInvoked).isFalse();
58+
assertThat(afterTransactionInvoked).isFalse();
59+
}
60+
61+
@BeforeTransaction
62+
void beforeTransaction(TestInfo testInfo, ApplicationContext context, @Autowired DataSource dataSource) {
63+
assertThatTransaction().isNotActive();
64+
assertThat(testInfo).isNotNull();
65+
assertThat(context).isNotNull();
66+
assertThat(dataSource).isNotNull();
67+
beforeTransactionInvoked = true;
68+
}
69+
70+
@Test
71+
@Transactional
72+
void transactionalTest(TestInfo testInfo, ApplicationContext context, @Autowired DataSource dataSource) {
73+
assertThatTransaction().isActive();
74+
assertThat(testInfo).isNotNull();
75+
assertThat(context).isNotNull();
76+
assertThat(dataSource).isNotNull();
77+
assertThat(beforeTransactionInvoked).isTrue();
78+
assertThat(afterTransactionInvoked).isFalse();
79+
}
80+
81+
@AfterTransaction
82+
void afterTransaction(TestInfo testInfo, ApplicationContext context, @Autowired DataSource dataSource) {
83+
assertThatTransaction().isNotActive();
84+
assertThat(testInfo).isNotNull();
85+
assertThat(context).isNotNull();
86+
assertThat(dataSource).isNotNull();
87+
afterTransactionInvoked = true;
88+
}
89+
90+
@AfterAll
91+
static void checkFinalFlagState() {
92+
assertThat(beforeTransactionInvoked).isTrue();
93+
assertThat(afterTransactionInvoked).isTrue();
94+
}
95+
96+
97+
@Configuration
98+
static class Config {
99+
100+
@Bean
101+
DataSourceTransactionManager transactionManager(DataSource dataSource) {
102+
return new DataSourceTransactionManager(dataSource);
103+
}
104+
105+
@Bean
106+
DataSource dataSource() {
107+
return new EmbeddedDatabaseBuilder().generateUniqueName(true).build();
108+
}
109+
}
110+
111+
}

0 commit comments

Comments
 (0)