Skip to content

Commit ab086f4

Browse files
antkorwinsbrannen
authored andcommitted
Fix fragile tests for asynchronous events
This commit introduces a dependency on the Awaitility assertion framework and makes use of asynchronous assertions in order to make tests for asynchronous events more robust. Issue: SPR-17211
1 parent 3268952 commit ab086f4

File tree

5 files changed

+143
-62
lines changed

5 files changed

+143
-62
lines changed

spring-context/spring-context.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ dependencies {
2828
testCompile("org.codehaus.groovy:groovy-test:${groovyVersion}")
2929
testCompile("org.apache.commons:commons-pool2:2.6.0")
3030
testCompile("javax.inject:javax.inject-tck:1")
31+
testCompile("org.awaitility:awaitility:3.1.2")
3132
testRuntime("javax.xml.bind:jaxb-api:2.3.0")
3233
testRuntime("org.glassfish:javax.el:3.0.1-b08")
3334
testRuntime("org.javamoney:moneta:1.3")

spring-context/src/test/java/org/springframework/context/support/SimpleThreadScopeTests.java

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package org.springframework.context.support;
1818

19+
import java.util.concurrent.TimeUnit;
20+
21+
import org.awaitility.Awaitility;
1922
import org.junit.Test;
2023

2124
import org.springframework.context.ApplicationContext;
@@ -45,26 +48,18 @@ public void getFromScope() throws Exception {
4548

4649
@Test
4750
public void getMultipleInstances() throws Exception {
51+
// Arrange
4852
final TestBean[] beans = new TestBean[2];
49-
Thread thread1 = new Thread(new Runnable() {
50-
@Override
51-
public void run() {
52-
beans[0] = applicationContext.getBean("threadScopedObject", TestBean.class);
53-
}
54-
});
55-
Thread thread2 = new Thread(new Runnable() {
56-
@Override
57-
public void run() {
58-
beans[1] = applicationContext.getBean("threadScopedObject", TestBean.class);
59-
}
60-
});
53+
Thread thread1 = new Thread(() -> beans[0] = applicationContext.getBean("threadScopedObject", TestBean.class));
54+
Thread thread2 = new Thread(() -> beans[1] = applicationContext.getBean("threadScopedObject", TestBean.class));
55+
// Act
6156
thread1.start();
6257
thread2.start();
63-
64-
Thread.sleep(200);
65-
66-
assertNotNull(beans[0]);
67-
assertNotNull(beans[1]);
58+
// Assert
59+
Awaitility.await()
60+
.pollInterval(10, TimeUnit.MILLISECONDS)
61+
.atMost(500, TimeUnit.MILLISECONDS)
62+
.until(() -> beans[0] != null & beans[1] != null);
6863
assertNotSame(beans[0], beans[1]);
6964
}
7065

spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@
2424
import java.util.concurrent.CompletableFuture;
2525
import java.util.concurrent.ExecutionException;
2626
import java.util.concurrent.Future;
27+
import java.util.concurrent.TimeUnit;
2728

2829
import org.aopalliance.intercept.MethodInterceptor;
2930
import org.aopalliance.intercept.MethodInvocation;
31+
import org.awaitility.Awaitility;
3032
import org.junit.Test;
3133

3234
import org.springframework.aop.framework.ProxyFactory;
@@ -365,35 +367,48 @@ public void dynamicAsyncMethodsInInterfaceWithPostProcessor() throws Exception {
365367

366368
@Test
367369
public void asyncMethodListener() throws Exception {
370+
// Arrange
368371
originalThreadName = Thread.currentThread().getName();
369372
listenerCalled = 0;
370373
GenericApplicationContext context = new GenericApplicationContext();
371374
context.registerBeanDefinition("asyncTest", new RootBeanDefinition(AsyncMethodListener.class));
372375
context.registerBeanDefinition("autoProxyCreator", new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class));
373376
context.registerBeanDefinition("asyncAdvisor", new RootBeanDefinition(AsyncAnnotationAdvisor.class));
377+
// Act
374378
context.refresh();
375-
Thread.sleep(1000);
376-
assertEquals(1, listenerCalled);
379+
// Assert
380+
Awaitility.await()
381+
.atMost(1, TimeUnit.SECONDS)
382+
.pollInterval(10, TimeUnit.MILLISECONDS)
383+
.until(() -> listenerCalled == 1);
384+
assertEquals(listenerCalled, 1);
377385
}
378386

379387
@Test
380388
public void asyncClassListener() throws Exception {
389+
// Arrange
381390
originalThreadName = Thread.currentThread().getName();
382391
listenerCalled = 0;
383392
listenerConstructed = 0;
384393
GenericApplicationContext context = new GenericApplicationContext();
385394
context.registerBeanDefinition("asyncTest", new RootBeanDefinition(AsyncClassListener.class));
386395
context.registerBeanDefinition("autoProxyCreator", new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class));
387396
context.registerBeanDefinition("asyncAdvisor", new RootBeanDefinition(AsyncAnnotationAdvisor.class));
397+
// Act
388398
context.refresh();
389399
context.close();
390-
Thread.sleep(1000);
400+
// Assert
401+
Awaitility.await()
402+
.atMost(1, TimeUnit.SECONDS)
403+
.pollInterval(10, TimeUnit.MILLISECONDS)
404+
.until(() -> listenerCalled == 2);
391405
assertEquals(2, listenerCalled);
392406
assertEquals(1, listenerConstructed);
393407
}
394408

395409
@Test
396410
public void asyncPrototypeClassListener() throws Exception {
411+
// Arrange
397412
originalThreadName = Thread.currentThread().getName();
398413
listenerCalled = 0;
399414
listenerConstructed = 0;
@@ -403,9 +418,14 @@ public void asyncPrototypeClassListener() throws Exception {
403418
context.registerBeanDefinition("asyncTest", listenerDef);
404419
context.registerBeanDefinition("autoProxyCreator", new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class));
405420
context.registerBeanDefinition("asyncAdvisor", new RootBeanDefinition(AsyncAnnotationAdvisor.class));
421+
// Act
406422
context.refresh();
407423
context.close();
408-
Thread.sleep(1000);
424+
// Assert
425+
Awaitility.await()
426+
.atMost(1, TimeUnit.SECONDS)
427+
.pollInterval(10, TimeUnit.MILLISECONDS)
428+
.until(() -> listenerCalled == 2);
409429
assertEquals(2, listenerCalled);
410430
assertEquals(2, listenerConstructed);
411431
}

spring-context/src/test/java/org/springframework/scheduling/annotation/EnableAsyncTests.java

Lines changed: 76 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
import java.util.concurrent.Executor;
2626
import java.util.concurrent.Executors;
2727
import java.util.concurrent.Future;
28+
import java.util.concurrent.TimeUnit;
2829

30+
import org.awaitility.Awaitility;
2931
import org.junit.Test;
3032
import org.mockito.Mockito;
3133

@@ -163,7 +165,7 @@ public void customAsyncAnnotationIsPropagated() {
163165
Object bean = ctx.getBean(CustomAsyncBean.class);
164166
assertTrue(AopUtils.isAopProxy(bean));
165167
boolean isAsyncAdvised = false;
166-
for (Advisor advisor : ((Advised)bean).getAdvisors()) {
168+
for (Advisor advisor : ((Advised) bean).getAdvisors()) {
167169
if (advisor instanceof AsyncAnnotationAdvisor) {
168170
isAsyncAdvised = true;
169171
break;
@@ -184,85 +186,129 @@ public void aspectModeAspectJAttemptsToRegisterAsyncAspect() {
184186

185187
@Test
186188
public void customExecutorBean() throws InterruptedException {
189+
// Arrange
187190
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
188191
ctx.register(CustomExecutorBean.class);
189192
ctx.refresh();
190-
191193
AsyncBean asyncBean = ctx.getBean(AsyncBean.class);
194+
// Act
192195
asyncBean.work();
193-
Thread.sleep(500);
196+
// Assert
197+
Awaitility.await()
198+
.atMost(500, TimeUnit.MILLISECONDS)
199+
.pollInterval(10, TimeUnit.MILLISECONDS)
200+
.until(() -> asyncBean.getThreadOfExecution() != null);
194201
assertThat(asyncBean.getThreadOfExecution().getName(), startsWith("Custom-"));
195-
196202
ctx.close();
197203
}
198204

199205
@Test
200206
public void customExecutorConfig() throws InterruptedException {
207+
// Arrange
201208
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
202209
ctx.register(CustomExecutorConfig.class);
203210
ctx.refresh();
204-
205211
AsyncBean asyncBean = ctx.getBean(AsyncBean.class);
212+
// Act
206213
asyncBean.work();
207-
Thread.sleep(500);
214+
// Assert
215+
Awaitility.await()
216+
.atMost(500, TimeUnit.MILLISECONDS)
217+
.pollInterval(10, TimeUnit.MILLISECONDS)
218+
.until(() -> asyncBean.getThreadOfExecution() != null);
208219
assertThat(asyncBean.getThreadOfExecution().getName(), startsWith("Custom-"));
220+
ctx.close();
221+
}
209222

210-
TestableAsyncUncaughtExceptionHandler exceptionHandler = (TestableAsyncUncaughtExceptionHandler)
211-
ctx.getBean("exceptionHandler");
223+
@Test
224+
public void customExecutorConfigWithThrowsException() {
225+
// Arrange
226+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
227+
ctx.register(CustomExecutorConfig.class);
228+
ctx.refresh();
229+
AsyncBean asyncBean = ctx.getBean(AsyncBean.class);
230+
Method method = ReflectionUtils.findMethod(AsyncBean.class, "fail");
231+
TestableAsyncUncaughtExceptionHandler exceptionHandler =
232+
(TestableAsyncUncaughtExceptionHandler) ctx.getBean("exceptionHandler");
212233
assertFalse("handler should not have been called yet", exceptionHandler.isCalled());
213-
234+
// Act
214235
asyncBean.fail();
215-
Thread.sleep(500);
216-
Method method = ReflectionUtils.findMethod(AsyncBean.class, "fail");
217-
exceptionHandler.assertCalledWith(method, UnsupportedOperationException.class);
218-
236+
// Assert
237+
Awaitility.await()
238+
.pollInterval(10, TimeUnit.MILLISECONDS)
239+
.atMost(500, TimeUnit.MILLISECONDS)
240+
.untilAsserted(() -> exceptionHandler.assertCalledWith(method, UnsupportedOperationException.class));
219241
ctx.close();
220242
}
221243

222244
@Test
223245
public void customExecutorBeanConfig() throws InterruptedException {
246+
// Arrange
224247
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
225248
ctx.register(CustomExecutorBeanConfig.class, ExecutorPostProcessor.class);
226249
ctx.refresh();
227-
228250
AsyncBean asyncBean = ctx.getBean(AsyncBean.class);
251+
// Act
229252
asyncBean.work();
230-
Thread.sleep(500);
253+
// Assert
254+
Awaitility.await()
255+
.pollInterval(10, TimeUnit.MILLISECONDS)
256+
.atMost(500, TimeUnit.MILLISECONDS)
257+
.until(() -> asyncBean.getThreadOfExecution() != null);
231258
assertThat(asyncBean.getThreadOfExecution().getName(), startsWith("Post-"));
259+
ctx.close();
260+
}
232261

233-
TestableAsyncUncaughtExceptionHandler exceptionHandler = (TestableAsyncUncaughtExceptionHandler)
234-
ctx.getBean("exceptionHandler");
262+
@Test
263+
public void customExecutorBeanConfigWithThrowsException() {
264+
// Arrange
265+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
266+
ctx.register(CustomExecutorBeanConfig.class, ExecutorPostProcessor.class);
267+
ctx.refresh();
268+
AsyncBean asyncBean = ctx.getBean(AsyncBean.class);
269+
TestableAsyncUncaughtExceptionHandler exceptionHandler =
270+
(TestableAsyncUncaughtExceptionHandler) ctx.getBean("exceptionHandler");
235271
assertFalse("handler should not have been called yet", exceptionHandler.isCalled());
236-
237-
asyncBean.fail();
238-
Thread.sleep(500);
239272
Method method = ReflectionUtils.findMethod(AsyncBean.class, "fail");
240-
exceptionHandler.assertCalledWith(method, UnsupportedOperationException.class);
241-
273+
// Act
274+
asyncBean.fail();
275+
// Assert
276+
Awaitility.await()
277+
.atMost(500, TimeUnit.MILLISECONDS)
278+
.pollInterval(10, TimeUnit.MILLISECONDS)
279+
.untilAsserted(() -> exceptionHandler.assertCalledWith(method, UnsupportedOperationException.class));
242280
ctx.close();
243281
}
244282

245283
@Test // SPR-14949
246284
public void findOnInterfaceWithInterfaceProxy() throws InterruptedException {
285+
// Arrange
247286
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Spr14949ConfigA.class);
248-
249287
AsyncInterface asyncBean = ctx.getBean(AsyncInterface.class);
288+
// Act
250289
asyncBean.work();
251-
Thread.sleep(500);
290+
// Assert
291+
Awaitility.await()
292+
.atMost(500, TimeUnit.MILLISECONDS)
293+
.pollInterval(10, TimeUnit.MILLISECONDS)
294+
.until(() -> asyncBean.getThreadOfExecution() != null);
252295
assertThat(asyncBean.getThreadOfExecution().getName(), startsWith("Custom-"));
253-
254296
ctx.close();
255297
}
256298

257299
@Test // SPR-14949
258300
public void findOnInterfaceWithCglibProxy() throws InterruptedException {
301+
// Arrange
259302
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Spr14949ConfigB.class);
260-
261303
AsyncInterface asyncBean = ctx.getBean(AsyncInterface.class);
304+
// Act
262305
asyncBean.work();
263-
Thread.sleep(500);
306+
// Assert
307+
Awaitility.await()
308+
.atMost(500, TimeUnit.MILLISECONDS)
309+
.pollInterval(10, TimeUnit.MILLISECONDS)
310+
.until(()-> asyncBean.getThreadOfExecution() != null);
264311
assertThat(asyncBean.getThreadOfExecution().getName(), startsWith("Custom-"));
265-
266312
ctx.close();
267313
}
268314

@@ -390,7 +436,8 @@ public AsyncBean asyncBean() {
390436
@EnableAsync
391437
static class AsyncConfigWithMockito {
392438

393-
@Bean @Lazy
439+
@Bean
440+
@Lazy
394441
public AsyncBean asyncBean() {
395442
return Mockito.mock(AsyncBean.class);
396443
}

0 commit comments

Comments
 (0)