Skip to content

Commit f7f37cd

Browse files
committed
Introduce before/after test execution callbacks in the TCF
This commit introduces 'before' and 'after' test execution callbacks in the Spring TestContext Framework. Specifically, this set of commits introduces the following. - beforeTestExecution() and afterTestExecution() callbacks in the TestExecutionListener API - beforeTestExecution() and afterTestExecution() callbacks in TestContextManager - RunBeforeTestExecutionCallbacks and RunAfterTestExecutionCallbacks JUnit 4 statements that are used by the SpringJUnit4ClassRunner - Documentation in the class-level Javadoc for SpringMethodRule stating that before/after test execution callbacks cannot be supported with JUnit 4 Rules - Support for JUnit Jupiter's BeforeTestExecutionCallback and AfterTestExecutionCallback extension APIs in the SpringExtension for JUnit 5 - Support for before/after test execution callbacks in AbstractTestNGSpringContextTests for TestNG Issue: SPR-4365
2 parents 11ed265 + 80018c6 commit f7f37cd

13 files changed

+994
-268
lines changed

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

Lines changed: 197 additions & 61 deletions
Large diffs are not rendered by default.

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

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
* @author Sam Brannen
5656
* @author Juergen Hoeller
5757
* @since 2.5
58+
* @see TestContextManager
5859
* @see org.springframework.test.context.support.AbstractTestExecutionListener
5960
*/
6061
public interface TestExecutionListener {
@@ -70,6 +71,7 @@ public interface TestExecutionListener {
7071
* concrete classes as necessary.
7172
* @param testContext the test context for the test; never {@code null}
7273
* @throws Exception allows any exception to propagate
74+
* @since 3.0
7375
*/
7476
default void beforeTestClass(TestContext testContext) throws Exception {
7577
/* no-op */
@@ -90,34 +92,90 @@ default void prepareTestInstance(TestContext testContext) throws Exception {
9092
}
9193

9294
/**
93-
* Pre-processes a test <em>before</em> execution of the
95+
* Pre-processes a test <em>before</em> execution of <em>before</em>
96+
* lifecycle callbacks of the underlying test framework &mdash; for example,
97+
* by setting up test fixtures.
98+
* <p>This method <strong>must</strong> be called immediately prior to
99+
* framework-specific <em>before</em> lifecycle callbacks. For historical
100+
* reasons, this method is named {@code beforeTestMethod}. Since the
101+
* introduction of {@link #beforeTestExecution}, a more suitable name for
102+
* this method might be something like {@code beforeTestSetUp} or
103+
* {@code beforeEach}; however, it is unfortunately impossible to rename
104+
* this method due to backward compatibility concerns.
105+
* <p>The default implementation is <em>empty</em>. Can be overridden by
106+
* concrete classes as necessary.
107+
* @param testContext the test context in which the test method will be
108+
* executed; never {@code null}
109+
* @throws Exception allows any exception to propagate
110+
* @see #afterTestMethod
111+
* @see #beforeTestExecution
112+
* @see #afterTestExecution
113+
*/
114+
default void beforeTestMethod(TestContext testContext) throws Exception {
115+
/* no-op */
116+
}
117+
118+
/**
119+
* Pre-processes a test <em>immediately before</em> execution of the
94120
* {@link java.lang.reflect.Method test method} in the supplied
95-
* {@link TestContext test context}, for example by setting up test
96-
* fixtures.
97-
* <p>This method should be called immediately prior to framework-specific
121+
* {@link TestContext test context} &mdash; for example, for timing
122+
* or logging purposes.
123+
* <p>This method <strong>must</strong> be called after framework-specific
98124
* <em>before</em> lifecycle callbacks.
99125
* <p>The default implementation is <em>empty</em>. Can be overridden by
100126
* concrete classes as necessary.
101127
* @param testContext the test context in which the test method will be
102128
* executed; never {@code null}
103129
* @throws Exception allows any exception to propagate
130+
* @since 5.0
131+
* @see #beforeTestMethod
132+
* @see #afterTestMethod
133+
* @see #afterTestExecution
104134
*/
105-
default void beforeTestMethod(TestContext testContext) throws Exception {
135+
default void beforeTestExecution(TestContext testContext) throws Exception {
106136
/* no-op */
107137
}
108138

109139
/**
110-
* Post-processes a test <em>after</em> execution of the
140+
* Post-processes a test <em>immediately after</em> execution of the
111141
* {@link java.lang.reflect.Method test method} in the supplied
112-
* {@link TestContext test context}, for example by tearing down test
113-
* fixtures.
114-
* <p>This method should be called immediately after framework-specific
142+
* {@link TestContext test context} &mdash; for example, for timing
143+
* or logging purposes.
144+
* <p>This method <strong>must</strong> be called before framework-specific
115145
* <em>after</em> lifecycle callbacks.
116146
* <p>The default implementation is <em>empty</em>. Can be overridden by
117147
* concrete classes as necessary.
148+
* @param testContext the test context in which the test method will be
149+
* executed; never {@code null}
150+
* @throws Exception allows any exception to propagate
151+
* @since 5.0
152+
* @see #beforeTestMethod
153+
* @see #afterTestMethod
154+
* @see #beforeTestExecution
155+
*/
156+
default void afterTestExecution(TestContext testContext) throws Exception {
157+
/* no-op */
158+
}
159+
160+
/**
161+
* Post-processes a test <em>after</em> execution of <em>after</em>
162+
* lifecycle callbacks of the underlying test framework &mdash; for example,
163+
* by tearing down test fixtures.
164+
* <p>This method <strong>must</strong> be called immediately after
165+
* framework-specific <em>after</em> lifecycle callbacks. For historical
166+
* reasons, this method is named {@code afterTestMethod}. Since the
167+
* introduction of {@link #afterTestExecution}, a more suitable name for
168+
* this method might be something like {@code afterTestTearDown} or
169+
* {@code afterEach}; however, it is unfortunately impossible to rename
170+
* this method due to backward compatibility concerns.
171+
* <p>The default implementation is <em>empty</em>. Can be overridden by
172+
* concrete classes as necessary.
118173
* @param testContext the test context in which the test method was
119174
* executed; never {@code null}
120175
* @throws Exception allows any exception to propagate
176+
* @see #beforeTestMethod
177+
* @see #beforeTestExecution
178+
* @see #afterTestExecution
121179
*/
122180
default void afterTestMethod(TestContext testContext) throws Exception {
123181
/* no-op */
@@ -134,6 +192,7 @@ default void afterTestMethod(TestContext testContext) throws Exception {
134192
* concrete classes as necessary.
135193
* @param testContext the test context for the test; never {@code null}
136194
* @throws Exception allows any exception to propagate
195+
* @since 3.0
137196
*/
138197
default void afterTestClass(TestContext testContext) throws Exception {
139198
/* no-op */

spring-test/src/main/java/org/springframework/test/context/junit/jupiter/SpringExtension.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323

2424
import org.junit.jupiter.api.extension.AfterAllCallback;
2525
import org.junit.jupiter.api.extension.AfterEachCallback;
26+
import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
2627
import org.junit.jupiter.api.extension.BeforeAllCallback;
2728
import org.junit.jupiter.api.extension.BeforeEachCallback;
29+
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
2830
import org.junit.jupiter.api.extension.ContainerExtensionContext;
2931
import org.junit.jupiter.api.extension.ExtensionContext;
3032
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
@@ -54,7 +56,8 @@
5456
* @see org.springframework.test.context.TestContextManager
5557
*/
5658
public class SpringExtension implements BeforeAllCallback, AfterAllCallback, TestInstancePostProcessor,
57-
BeforeEachCallback, AfterEachCallback, ParameterResolver {
59+
BeforeEachCallback, AfterEachCallback, BeforeTestExecutionCallback, AfterTestExecutionCallback,
60+
ParameterResolver {
5861

5962
/**
6063
* {@link Namespace} in which {@code TestContextManagers} are stored, keyed
@@ -101,6 +104,27 @@ public void beforeEach(TestExtensionContext context) throws Exception {
101104
getTestContextManager(context).beforeTestMethod(testInstance, testMethod);
102105
}
103106

107+
/**
108+
* Delegates to {@link TestContextManager#beforeTestExecution}.
109+
*/
110+
@Override
111+
public void beforeTestExecution(TestExtensionContext context) throws Exception {
112+
Object testInstance = context.getTestInstance();
113+
Method testMethod = context.getTestMethod().get();
114+
getTestContextManager(context).beforeTestExecution(testInstance, testMethod);
115+
}
116+
117+
/**
118+
* Delegates to {@link TestContextManager#afterTestExecution}.
119+
*/
120+
@Override
121+
public void afterTestExecution(TestExtensionContext context) throws Exception {
122+
Object testInstance = context.getTestInstance();
123+
Method testMethod = context.getTestMethod().get();
124+
Throwable testException = context.getTestException().orElse(null);
125+
getTestContextManager(context).afterTestExecution(testInstance, testMethod, testException);
126+
}
127+
104128
/**
105129
* Delegates to {@link TestContextManager#afterTestMethod}.
106130
*/

spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,10 @@
4242
import org.springframework.test.context.junit4.rules.SpringClassRule;
4343
import org.springframework.test.context.junit4.rules.SpringMethodRule;
4444
import org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks;
45+
import org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks;
4546
import org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks;
4647
import org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks;
48+
import org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks;
4749
import org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks;
4850
import org.springframework.test.context.junit4.statements.SpringFailOnTimeout;
4951
import org.springframework.test.context.junit4.statements.SpringRepeat;
@@ -270,6 +272,9 @@ protected void runChild(FrameworkMethod frameworkMethod, RunNotifier notifier) {
270272
* Spring-specific timeouts in that the former execute in a separate
271273
* thread while the latter simply execute in the main thread (like regular
272274
* tests).
275+
* @see #methodInvoker(FrameworkMethod, Object)
276+
* @see #withBeforeTestExecutionCallbacks(FrameworkMethod, Object, Statement)
277+
* @see #withAfterTestExecutionCallbacks(FrameworkMethod, Object, Statement)
273278
* @see #possiblyExpectingExceptions(FrameworkMethod, Object, Statement)
274279
* @see #withBefores(FrameworkMethod, Object, Statement)
275280
* @see #withAfters(FrameworkMethod, Object, Statement)
@@ -293,6 +298,8 @@ protected Object runReflectiveCall() throws Throwable {
293298
}
294299

295300
Statement statement = methodInvoker(frameworkMethod, testInstance);
301+
statement = withBeforeTestExecutionCallbacks(frameworkMethod, testInstance, statement);
302+
statement = withAfterTestExecutionCallbacks(frameworkMethod, testInstance, statement);
296303
statement = possiblyExpectingExceptions(frameworkMethod, testInstance, statement);
297304
statement = withBefores(frameworkMethod, testInstance, statement);
298305
statement = withAfters(frameworkMethod, testInstance, statement);
@@ -404,6 +411,26 @@ protected long getSpringTimeout(FrameworkMethod frameworkMethod) {
404411
return TestAnnotationUtils.getTimeout(frameworkMethod.getMethod());
405412
}
406413

414+
/**
415+
* Wrap the supplied {@link Statement} with a {@code RunBeforeTestExecutionCallbacks}
416+
* statement, thus preserving the default functionality while adding support for the
417+
* Spring TestContext Framework.
418+
* @see RunBeforeTestExecutionCallbacks
419+
*/
420+
protected Statement withBeforeTestExecutionCallbacks(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) {
421+
return new RunBeforeTestExecutionCallbacks(statement, testInstance, frameworkMethod.getMethod(), getTestContextManager());
422+
}
423+
424+
/**
425+
* Wrap the supplied {@link Statement} with a {@code RunAfterTestExecutionCallbacks}
426+
* statement, thus preserving the default functionality while adding support for the
427+
* Spring TestContext Framework.
428+
* @see RunAfterTestExecutionCallbacks
429+
*/
430+
protected Statement withAfterTestExecutionCallbacks(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) {
431+
return new RunAfterTestExecutionCallbacks(statement, testInstance, frameworkMethod.getMethod(), getTestContextManager());
432+
}
433+
407434
/**
408435
* Wrap the {@link Statement} returned by the parent implementation with a
409436
* {@code RunBeforeTestMethodCallbacks} statement, thus preserving the
@@ -414,8 +441,7 @@ protected long getSpringTimeout(FrameworkMethod frameworkMethod) {
414441
@Override
415442
protected Statement withBefores(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) {
416443
Statement junitBefores = super.withBefores(frameworkMethod, testInstance, statement);
417-
return new RunBeforeTestMethodCallbacks(junitBefores, testInstance, frameworkMethod.getMethod(),
418-
getTestContextManager());
444+
return new RunBeforeTestMethodCallbacks(junitBefores, testInstance, frameworkMethod.getMethod(), getTestContextManager());
419445
}
420446

421447
/**
@@ -428,8 +454,7 @@ protected Statement withBefores(FrameworkMethod frameworkMethod, Object testInst
428454
@Override
429455
protected Statement withAfters(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) {
430456
Statement junitAfters = super.withAfters(frameworkMethod, testInstance, statement);
431-
return new RunAfterTestMethodCallbacks(junitAfters, testInstance, frameworkMethod.getMethod(),
432-
getTestContextManager());
457+
return new RunAfterTestMethodCallbacks(junitAfters, testInstance, frameworkMethod.getMethod(), getTestContextManager());
433458
}
434459

435460
/**

spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringMethodRule.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 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.
@@ -37,7 +37,7 @@
3737
import org.springframework.util.ReflectionUtils;
3838

3939
/**
40-
* {@code SpringMethodRule} is a custom JUnit {@link MethodRule} that
40+
* {@code SpringMethodRule} is a custom JUnit 4 {@link MethodRule} that
4141
* supports instance-level and method-level features of the
4242
* <em>Spring TestContext Framework</em> in standard JUnit tests by means
4343
* of the {@link TestContextManager} and associated support classes and
@@ -82,6 +82,12 @@
8282
*
8383
* <p><strong>NOTE:</strong> As of Spring Framework 4.3, this class requires JUnit 4.12 or higher.
8484
*
85+
* <p><strong>WARNING:</strong> Due to the shortcomings of JUnit rules, the
86+
* {@code SpringMethodRule} does <strong>not</strong> support the
87+
* {@code beforeTestExecution()} and {@code afterTestExecution()} callbacks of the
88+
* {@link org.springframework.test.context.TestExecutionListener TestExecutionListener}
89+
* API.
90+
*
8591
* @author Sam Brannen
8692
* @author Philippe Marschall
8793
* @since 4.2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright 2002-2016 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+
* http://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.junit4.statements;
18+
19+
import java.lang.reflect.Method;
20+
import java.util.ArrayList;
21+
import java.util.List;
22+
23+
import org.junit.runners.model.MultipleFailureException;
24+
import org.junit.runners.model.Statement;
25+
import org.springframework.test.context.TestContextManager;
26+
27+
/**
28+
* {@code RunAfterTestExecutionCallbacks} is a custom JUnit {@link Statement}
29+
* which allows the <em>Spring TestContext Framework</em> to be plugged into the
30+
* JUnit 4 execution chain by calling {@link TestContextManager#afterTestExecution
31+
* afterTestExecution()} on the supplied {@link TestContextManager}.
32+
*
33+
* <p><strong>NOTE:</strong> This class requires JUnit 4.9 or higher.
34+
*
35+
* @author Sam Brannen
36+
* @since 5.0
37+
* @see #evaluate()
38+
* @see RunBeforeTestExecutionCallbacks
39+
*/
40+
public class RunAfterTestExecutionCallbacks extends Statement {
41+
42+
private final Statement next;
43+
44+
private final Object testInstance;
45+
46+
private final Method testMethod;
47+
48+
private final TestContextManager testContextManager;
49+
50+
51+
/**
52+
* Construct a new {@code RunAfterTestExecutionCallbacks} statement.
53+
* @param next the next {@code Statement} in the execution chain
54+
* @param testInstance the current test instance (never {@code null})
55+
* @param testMethod the test method which has just been executed on the
56+
* test instance
57+
* @param testContextManager the TestContextManager upon which to call
58+
* {@code afterTestExecution()}
59+
*/
60+
public RunAfterTestExecutionCallbacks(Statement next, Object testInstance, Method testMethod,
61+
TestContextManager testContextManager) {
62+
63+
this.next = next;
64+
this.testInstance = testInstance;
65+
this.testMethod = testMethod;
66+
this.testContextManager = testContextManager;
67+
}
68+
69+
/**
70+
* Evaluate the next {@link Statement} in the execution chain (typically an
71+
* instance of {@link RunBeforeTestExecutionCallbacks}), catching any exceptions
72+
* thrown, and then invoke {@link TestContextManager#afterTestExecution} supplying
73+
* the first caught exception (if any).
74+
* <p>If the invocation of {@code afterTestExecution()} throws an exception, that
75+
* exception will also be tracked. Multiple exceptions will be combined into a
76+
* {@link MultipleFailureException}.
77+
*/
78+
@Override
79+
public void evaluate() throws Throwable {
80+
Throwable testException = null;
81+
List<Throwable> errors = new ArrayList<>();
82+
try {
83+
this.next.evaluate();
84+
}
85+
catch (Throwable ex) {
86+
testException = ex;
87+
errors.add(ex);
88+
}
89+
90+
try {
91+
this.testContextManager.afterTestExecution(this.testInstance, this.testMethod, testException);
92+
}
93+
catch (Throwable ex) {
94+
errors.add(ex);
95+
}
96+
97+
MultipleFailureException.assertEmpty(errors);
98+
}
99+
100+
}

0 commit comments

Comments
 (0)