Skip to content

Commit 7ec85a3

Browse files
committed
Support suppressed exceptions in the TestContext framework
Prior to this commit, if multiple TestExecutionListener 'after' methods threw an exception, only the first such exception was rethrown. Subsequent exceptions were logged, but there was no way to access or process them other than via the log file. This commit addresses this shortcoming by making use of the support for suppressed exceptions introduced in Java 7. Specifically, if multiple TestExecutionListener 'after' methods throw an exception, the first exception will be rethrown with subsequent exceptions suppressed in the first one. Issue: SPR-14459
1 parent 5f176f5 commit 7ec85a3

File tree

2 files changed

+149
-0
lines changed

2 files changed

+149
-0
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,9 @@ public void afterTestExecution(Object testInstance, Method testMethod, Throwable
368368
if (afterTestExecutionException == null) {
369369
afterTestExecutionException = ex;
370370
}
371+
else {
372+
afterTestExecutionException.addSuppressed(ex);
373+
}
371374
}
372375
}
373376
if (afterTestExecutionException != null) {
@@ -423,6 +426,9 @@ public void afterTestMethod(Object testInstance, Method testMethod, Throwable ex
423426
if (afterTestMethodException == null) {
424427
afterTestMethodException = ex;
425428
}
429+
else {
430+
afterTestMethodException.addSuppressed(ex);
431+
}
426432
}
427433
}
428434
if (afterTestMethodException != null) {
@@ -464,6 +470,9 @@ public void afterTestClass() throws Exception {
464470
if (afterTestClassException == null) {
465471
afterTestClassException = ex;
466472
}
473+
else {
474+
afterTestClassException.addSuppressed(ex);
475+
}
467476
}
468477
}
469478
if (afterTestClassException != null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
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;
18+
19+
import java.lang.reflect.Method;
20+
21+
import org.junit.Test;
22+
23+
import static org.junit.Assert.*;
24+
25+
/**
26+
* JUnit 4 based unit tests for {@link TestContextManager}, which verify proper
27+
* support for <em>suppressed exceptions</em> thrown by {@link TestExecutionListener
28+
* TestExecutionListeners}.
29+
*
30+
* @author Sam Brannen
31+
* @since 5.0
32+
* @see Throwable#getSuppressed()
33+
*/
34+
public class TestContextManagerSuppressedExceptionsTests {
35+
36+
@Test
37+
public void afterTestExecution() throws Exception {
38+
test("afterTestExecution", FailingAfterTestExecutionTestCase.class,
39+
(tcm, c, m) -> tcm.afterTestExecution(this, m, null));
40+
}
41+
42+
@Test
43+
public void afterTestMethod() throws Exception {
44+
test("afterTestMethod", FailingAfterTestMethodTestCase.class,
45+
(tcm, c, m) -> tcm.afterTestMethod(this, m, null));
46+
}
47+
48+
@Test
49+
public void afterTestClass() throws Exception {
50+
test("afterTestClass", FailingAfterTestClassTestCase.class, (tcm, c, m) -> tcm.afterTestClass());
51+
}
52+
53+
private void test(String useCase, Class<?> testClass, Callback callback) throws Exception {
54+
TestContextManager testContextManager = new TestContextManager(testClass);
55+
assertEquals("Registered TestExecutionListeners", 2, testContextManager.getTestExecutionListeners().size());
56+
57+
try {
58+
Method testMethod = getClass().getMethod("toString");
59+
callback.invoke(testContextManager, testClass, testMethod);
60+
fail("should have thrown an AssertionError");
61+
}
62+
catch (AssertionError err) {
63+
// 'after' callbacks are reversed, so 2 comes before 1.
64+
assertEquals(useCase + "-2", err.getMessage());
65+
Throwable[] suppressed = err.getSuppressed();
66+
assertEquals(1, suppressed.length);
67+
assertEquals(useCase + "-1", suppressed[0].getMessage());
68+
}
69+
}
70+
71+
72+
// -------------------------------------------------------------------
73+
74+
@FunctionalInterface
75+
private interface Callback {
76+
77+
void invoke(TestContextManager tcm, Class<?> clazz, Method method) throws Exception;
78+
}
79+
80+
private static class FailingAfterTestClassListener1 implements TestExecutionListener {
81+
82+
@Override
83+
public void afterTestClass(TestContext testContext) {
84+
fail("afterTestClass-1");
85+
}
86+
}
87+
88+
private static class FailingAfterTestClassListener2 implements TestExecutionListener {
89+
90+
@Override
91+
public void afterTestClass(TestContext testContext) {
92+
fail("afterTestClass-2");
93+
}
94+
}
95+
96+
private static class FailingAfterTestMethodListener1 implements TestExecutionListener {
97+
98+
@Override
99+
public void afterTestMethod(TestContext testContext) {
100+
fail("afterTestMethod-1");
101+
}
102+
}
103+
104+
private static class FailingAfterTestMethodListener2 implements TestExecutionListener {
105+
106+
@Override
107+
public void afterTestMethod(TestContext testContext) {
108+
fail("afterTestMethod-2");
109+
}
110+
}
111+
112+
private static class FailingAfterTestExecutionListener1 implements TestExecutionListener {
113+
114+
@Override
115+
public void afterTestExecution(TestContext testContext) {
116+
fail("afterTestExecution-1");
117+
}
118+
}
119+
120+
private static class FailingAfterTestExecutionListener2 implements TestExecutionListener {
121+
122+
@Override
123+
public void afterTestExecution(TestContext testContext) {
124+
fail("afterTestExecution-2");
125+
}
126+
}
127+
128+
@TestExecutionListeners({ FailingAfterTestExecutionListener1.class, FailingAfterTestExecutionListener2.class })
129+
private static class FailingAfterTestExecutionTestCase {
130+
}
131+
132+
@TestExecutionListeners({ FailingAfterTestMethodListener1.class, FailingAfterTestMethodListener2.class })
133+
private static class FailingAfterTestMethodTestCase {
134+
}
135+
136+
@TestExecutionListeners({ FailingAfterTestClassListener1.class, FailingAfterTestClassListener2.class })
137+
private static class FailingAfterTestClassTestCase {
138+
}
139+
140+
}

0 commit comments

Comments
 (0)