17
17
package org .springframework .test .context .event ;
18
18
19
19
import java .lang .annotation .Retention ;
20
+ import java .lang .annotation .Target ;
20
21
import java .lang .reflect .Method ;
22
+ import java .util .concurrent .CountDownLatch ;
23
+ import java .util .concurrent .Executor ;
24
+ import java .util .concurrent .TimeUnit ;
21
25
22
26
import org .junit .Before ;
27
+ import org .junit .Rule ;
23
28
import org .junit .Test ;
29
+ import org .junit .rules .ExpectedException ;
30
+ import org .junit .runner .RunWith ;
24
31
32
+ import org .springframework .aop .interceptor .AsyncUncaughtExceptionHandler ;
25
33
import org .springframework .context .annotation .Bean ;
26
34
import org .springframework .context .annotation .Configuration ;
35
+ import org .springframework .scheduling .annotation .Async ;
36
+ import org .springframework .scheduling .annotation .AsyncConfigurerSupport ;
37
+ import org .springframework .scheduling .annotation .EnableAsync ;
38
+ import org .springframework .scheduling .concurrent .ThreadPoolTaskExecutor ;
39
+ import org .springframework .stereotype .Component ;
27
40
import org .springframework .test .context .ContextConfiguration ;
28
41
import org .springframework .test .context .TestContext ;
29
42
import org .springframework .test .context .TestContextManager ;
36
49
import org .springframework .test .context .event .annotation .BeforeTestExecution ;
37
50
import org .springframework .test .context .event .annotation .BeforeTestMethod ;
38
51
import org .springframework .test .context .event .annotation .PrepareTestInstance ;
52
+ import org .springframework .test .context .junit4 .SpringRunner ;
39
53
import org .springframework .util .ReflectionUtils ;
40
54
55
+ import static java .lang .annotation .ElementType .METHOD ;
41
56
import static java .lang .annotation .RetentionPolicy .RUNTIME ;
57
+ import static org .hamcrest .CoreMatchers .equalTo ;
58
+ import static org .hamcrest .CoreMatchers .startsWith ;
59
+ import static org .junit .Assert .assertThat ;
42
60
import static org .mockito .Mockito .mock ;
43
61
import static org .mockito .Mockito .never ;
44
62
import static org .mockito .Mockito .only ;
45
63
import static org .mockito .Mockito .reset ;
46
64
import static org .mockito .Mockito .verify ;
65
+ import static org .springframework .test .context .TestExecutionListeners .MergeMode .MERGE_WITH_DEFAULTS ;
47
66
48
67
/**
49
68
* Integration tests for {@link EventPublishingTestExecutionListener} and
55
74
*/
56
75
public class EventPublishingTestExecutionListenerIntegrationTests {
57
76
77
+ private static final String THREAD_NAME_PREFIX = "Test-" ;
78
+
79
+ private static final CountDownLatch countDownLatch = new CountDownLatch (1 );
80
+
58
81
private final TestContextManager testContextManager = new TestContextManager (ExampleTestCase .class );
59
82
private final TestContext testContext = testContextManager .getTestContext ();
60
- private final TestExecutionListener listener = testContext .getApplicationContext ().getBean (EventCaptureConfiguration .class ). listener ( );
83
+ private final TestExecutionListener listener = testContext .getApplicationContext ().getBean (TestExecutionListener .class );
61
84
private final Object testInstance = new ExampleTestCase ();
62
- private final Method testMethod = ReflectionUtils .findMethod (ExampleTestCase .class , "test1" );
85
+ private final Method testMethod = ReflectionUtils .findMethod (ExampleTestCase .class , "traceableTest" );
86
+
87
+ @ Rule
88
+ public final ExpectedException exception = ExpectedException .none ();
63
89
64
90
65
91
@ Before
@@ -82,18 +108,49 @@ public void prepareTestInstanceAnnotation() throws Exception {
82
108
}
83
109
84
110
@ Test
85
- public void beforeTestMethodAnnotationWithMatchingCondition () throws Exception {
111
+ public void beforeTestMethodAnnotation () throws Exception {
86
112
testContextManager .beforeTestMethod (testInstance , testMethod );
87
113
verify (listener , only ()).beforeTestMethod (testContext );
88
114
}
89
115
90
116
@ Test
91
117
public void beforeTestMethodAnnotationWithFailingCondition () throws Exception {
92
- Method testMethod2 = ReflectionUtils .findMethod (ExampleTestCase .class , "test2 " );
93
- testContextManager .beforeTestMethod (testInstance , testMethod2 );
118
+ Method standardTest = ReflectionUtils .findMethod (ExampleTestCase .class , "standardTest " );
119
+ testContextManager .beforeTestMethod (testInstance , standardTest );
94
120
verify (listener , never ()).beforeTestMethod (testContext );
95
121
}
96
122
123
+ @ Test
124
+ public void beforeTestMethodAnnotationWithFailingEventListener () throws Exception {
125
+ Method method = ReflectionUtils .findMethod (ExampleTestCase .class , "testWithFailingEventListener" );
126
+
127
+ exception .expect (RuntimeException .class );
128
+ exception .expectMessage ("Boom!" );
129
+
130
+ try {
131
+ testContextManager .beforeTestMethod (testInstance , method );
132
+ }
133
+ finally {
134
+ verify (listener , only ()).beforeTestMethod (testContext );
135
+ }
136
+ }
137
+
138
+ @ Test
139
+ public void beforeTestMethodAnnotationWithFailingAsyncEventListener () throws Exception {
140
+ TrackingAsyncUncaughtExceptionHandler .asyncException = null ;
141
+
142
+ String methodName = "testWithFailingAsyncEventListener" ;
143
+ Method method = ReflectionUtils .findMethod (ExampleTestCase .class , methodName );
144
+
145
+ testContextManager .beforeTestMethod (testInstance , method );
146
+
147
+ assertThat (countDownLatch .await (2 , TimeUnit .SECONDS ), equalTo (true ));
148
+
149
+ verify (listener , only ()).beforeTestMethod (testContext );
150
+ assertThat (TrackingAsyncUncaughtExceptionHandler .asyncException .getMessage (),
151
+ startsWith ("Asynchronous exception for test method [" + methodName + "] in thread [" + THREAD_NAME_PREFIX ));
152
+ }
153
+
97
154
@ Test
98
155
public void beforeTestExecutionAnnotation () throws Exception {
99
156
testContextManager .beforeTestExecution (testInstance , testMethod );
@@ -119,66 +176,142 @@ public void afterTestClassAnnotation() throws Exception {
119
176
}
120
177
121
178
179
+ @ Target (METHOD )
180
+ @ Retention (RUNTIME )
181
+ @interface Traceable {
182
+ }
183
+
184
+ @ RunWith (SpringRunner .class )
185
+ @ ContextConfiguration (classes = EventCaptureConfiguration .class )
186
+ @ TestExecutionListeners (listeners = EventPublishingTestExecutionListener .class , mergeMode = MERGE_WITH_DEFAULTS )
187
+ public static class ExampleTestCase {
188
+
189
+ @ Traceable
190
+ @ Test
191
+ public void traceableTest () {
192
+ /* no-op */
193
+ }
194
+
195
+ @ Test
196
+ public void standardTest () {
197
+ /* no-op */
198
+ }
199
+
200
+ @ Test
201
+ public void testWithFailingEventListener () {
202
+ /* no-op */
203
+ }
204
+
205
+ @ Test
206
+ public void testWithFailingAsyncEventListener () {
207
+ /* no-op */
208
+ }
209
+
210
+ }
211
+
122
212
@ Configuration
123
- static class EventCaptureConfiguration {
213
+ @ EnableAsync (proxyTargetClass = true )
214
+ static class EventCaptureConfiguration extends AsyncConfigurerSupport {
215
+
216
+ @ Override
217
+ public Executor getAsyncExecutor () {
218
+ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor ();
219
+ executor .setThreadNamePrefix (THREAD_NAME_PREFIX );
220
+ executor .initialize ();
221
+ return executor ;
222
+ }
223
+
224
+ @ Override
225
+ public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler () {
226
+ return new TrackingAsyncUncaughtExceptionHandler ();
227
+ }
124
228
125
229
@ Bean
126
230
public TestExecutionListener listener () {
127
231
return mock (TestExecutionListener .class );
128
232
}
129
233
234
+ @ Bean
235
+ EventCaptureBean eventCaptureBean () {
236
+ return new EventCaptureBean (listener ());
237
+ }
238
+
239
+ }
240
+
241
+ static class TrackingAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
242
+
243
+ static volatile Throwable asyncException ;
244
+
245
+
246
+ @ Override
247
+ public void handleUncaughtException (Throwable exception , Method method , Object ... params ) {
248
+ asyncException = exception ;
249
+ countDownLatch .countDown ();
250
+ }
251
+ }
252
+
253
+ // MUST be annotated with @Component due to a change in Spring 5.1 that
254
+ // does not consider beans in a package starting with "org.springframework"
255
+ // to be event listeners unless they are also components. See
256
+ // org.springframework.context.event.EventListenerMethodProcessor.isSpringContainerClass(Class<?>)
257
+ // for details.
258
+ @ Component
259
+ static class EventCaptureBean {
260
+
261
+ final TestExecutionListener listener ;
262
+
263
+
264
+ EventCaptureBean (TestExecutionListener listener ) {
265
+ this .listener = listener ;
266
+ }
267
+
130
268
@ BeforeTestClass ("#root.event.source.testClass.name matches '.+TestCase'" )
131
269
public void beforeTestClass (BeforeTestClassEvent e ) throws Exception {
132
- listener () .beforeTestClass (e .getSource ());
270
+ this . listener .beforeTestClass (e .getSource ());
133
271
}
134
272
135
273
@ PrepareTestInstance ("#a0.testContext.testClass.name matches '.+TestCase'" )
136
274
public void prepareTestInstance (PrepareTestInstanceEvent e ) throws Exception {
137
- listener () .prepareTestInstance (e .getSource ());
275
+ this . listener .prepareTestInstance (e .getSource ());
138
276
}
139
277
140
278
@ BeforeTestMethod ("#p0.testContext.testMethod.isAnnotationPresent(T(org.springframework.test.context.event.EventPublishingTestExecutionListenerIntegrationTests.Traceable))" )
141
279
public void beforeTestMethod (BeforeTestMethodEvent e ) throws Exception {
142
- listener ().beforeTestMethod (e .getSource ());
280
+ this .listener .beforeTestMethod (e .getSource ());
281
+ }
282
+
283
+ @ BeforeTestMethod ("event.testContext.testMethod.name == 'testWithFailingEventListener'" )
284
+ public void beforeTestMethodWithFailure (BeforeTestMethodEvent event ) throws Exception {
285
+ this .listener .beforeTestMethod (event .getSource ());
286
+ throw new RuntimeException ("Boom!" );
287
+ }
288
+
289
+ @ BeforeTestMethod ("event.testContext.testMethod.name == 'testWithFailingAsyncEventListener'" )
290
+ @ Async
291
+ public void beforeTestMethodWithAsyncFailure (BeforeTestMethodEvent event ) throws Exception {
292
+ this .listener .beforeTestMethod (event .getSource ());
293
+ throw new RuntimeException (String .format ("Asynchronous exception for test method [%s] in thread [%s]" ,
294
+ event .getTestContext ().getTestMethod ().getName (), Thread .currentThread ().getName ()));
143
295
}
144
296
145
297
@ BeforeTestExecution
146
298
public void beforeTestExecution (BeforeTestExecutionEvent e ) throws Exception {
147
- listener () .beforeTestExecution (e .getSource ());
299
+ this . listener .beforeTestExecution (e .getSource ());
148
300
}
149
301
150
302
@ AfterTestExecution
151
303
public void afterTestExecution (AfterTestExecutionEvent e ) throws Exception {
152
- listener () .afterTestExecution (e .getSource ());
304
+ this . listener .afterTestExecution (e .getSource ());
153
305
}
154
306
155
307
@ AfterTestMethod ("event.testContext.testMethod.isAnnotationPresent(T(org.springframework.test.context.event.EventPublishingTestExecutionListenerIntegrationTests.Traceable))" )
156
308
public void afterTestMethod (AfterTestMethodEvent e ) throws Exception {
157
- listener () .afterTestMethod (e .getSource ());
309
+ this . listener .afterTestMethod (e .getSource ());
158
310
}
159
311
160
312
@ AfterTestClass ("#afterTestClassEvent.testContext.testClass.name matches '.+TestCase'" )
161
313
public void afterTestClass (AfterTestClassEvent afterTestClassEvent ) throws Exception {
162
- listener ().afterTestClass (afterTestClassEvent .getSource ());
163
- }
164
-
165
- }
166
-
167
- @ Retention (RUNTIME )
168
- @interface Traceable {
169
- }
170
-
171
- @ ContextConfiguration (classes = EventCaptureConfiguration .class )
172
- @ TestExecutionListeners (EventPublishingTestExecutionListener .class )
173
- static class ExampleTestCase {
174
-
175
- @ Traceable
176
- public void test1 () {
177
- /* no-op */
178
- }
179
-
180
- public void test2 () {
181
- /* no-op */
314
+ this .listener .afterTestClass (afterTestClassEvent .getSource ());
182
315
}
183
316
}
184
317
0 commit comments