我有一个Spring服务:
@Service
@Transactional
public class SomeService {
@Async
public void asyncMethod(Foo foo) {
// processing takes significant time
}
}
我对此SomeService
进行了集成测试:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
@IntegrationTest
@Transactional
public class SomeServiceIntTest {
@Inject
private SomeService someService;
@Test
public void testAsyncMethod() {
Foo testData = prepareTestData();
someService.asyncMethod(testData);
verifyResults();
}
// verifyResult() with assertions, etc.
}
问题在于:
SomeService.asyncMethod(..)
注释为@Async
和SpringJUnit4ClassRunner
遵守@Async
语义 testAsyncMethod
线程将调用someService.asyncMethod(testData)
分叉到自己的工作线程中,然后直接继续执行verifyResults()
,可能在前一个工作线程完成其工作之前。
如何在验证结果之前等待someService.asyncMethod(testData)
完成?请注意, How do I write a unit test to verify async behavior using Spring 4 and annotations? 的解决方案不适用于此处,因为someService.asyncMethod(testData)
会返回void
,而不是Future<?>
。
答案 0 :(得分:15)
要遵守@Async
语义,some active @Configuration
class will have the @EnableAsync
annotation,例如
@Configuration
@EnableAsync
@EnableScheduling
public class AsyncConfiguration implements AsyncConfigurer {
//
}
为了解决我的问题,我引入了一个新的Spring配置文件non-async
。
如果non-async
个人资料不有效,则会使用AsyncConfiguration
:
@Configuration
@EnableAsync
@EnableScheduling
@Profile("!non-async")
public class AsyncConfiguration implements AsyncConfigurer {
// this configuration will be active as long as profile "non-async" is not (!) active
}
如果非同步配置文件 有效,则使用NonAsyncConfiguration
:
@Configuration
// notice the missing @EnableAsync annotation
@EnableScheduling
@Profile("non-async")
public class NonAsyncConfiguration {
// this configuration will be active as long as profile "non-async" is active
}
现在在有问题的JUnit测试类中,我明确激活了“非异步”配置文件,以便相互排除异步行为:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
@IntegrationTest
@Transactional
@ActiveProfiles(profiles = "non-async")
public class SomeServiceIntTest {
@Inject
private SomeService someService;
@Test
public void testAsyncMethod() {
Foo testData = prepareTestData();
someService.asyncMethod(testData);
verifyResults();
}
// verifyResult() with assertions, etc.
}
答案 1 :(得分:7)
如果您正在使用Mockito(直接或通过Spring测试支持@MockBean
),则它具有完全针对此情况的超时验证模式:
https://static.javadoc.io/org.mockito/mockito-core/2.10.0/org/mockito/Mockito.html#22
someAsyncCall();
verify(mock, timeout(100)).someMethod();
您也可以使用Awaitility(在互联网上找到它,没有尝试过)。 https://blog.jayway.com/2014/04/23/java-8-and-assertj-support-in-awaitility-1-6-0/
someAsyncCall();
await().until( () -> assertThat(userRepo.size()).isEqualTo(1) );
答案 2 :(得分:3)
我已经通过注射完成了 ThreadPoolTaskExecutor
然后
executor.getThreadPoolExecutor()。awaitTermination(1,TimeUnit.SECONDS);
在验证结果之前, 如下:
@Autowired
private ThreadPoolTaskExecutor executor;
@Test
public void testAsyncMethod() {
Foo testData = prepareTestData();
someService.asyncMethod(testData);
executor.getThreadPoolExecutor().awaitTermination(1, TimeUnit.SECONDS);
verifyResults();
}
答案 3 :(得分:1)
如果您的方法返回CompletableFuture
,请使用join
方法 - documentation CompletableFuture::join。
此方法等待异步方法完成并返回结果。在主线程中重新抛出任何遇到的异常。
答案 4 :(得分:1)
只是对上述解决方案的补充:
@Autowired
private ThreadPoolTaskExecutor pool;
@Test
public void testAsyncMethod() {
// call async method
someService.asyncMethod(testData);
boolean awaitTermination = pool.getThreadPoolExecutor().awaitTermination(1, TimeUnit.SECONDS);
assertThat(awaitTermination).isFalse();
// verify results
}
答案 5 :(得分:0)
只是为了扩展@bastiat 的答案,在我看来这应该被认为是正确的,如果您正在与多个执行程序一起工作,您还应该指定 TaskExecutor
。所以你需要注入你希望等待的正确的。所以,让我们假设我们有以下 configuration 类。
@Configuration
@EnableAsync
public class AsyncConfiguration {
@Bean("myTaskExecutor")
public TaskExecutor myTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(15);
executor.setCoreCapacity(10);
executor.setQueueCapacity(Integer.MAX_VALUE);
executor.setThreadNamePrefix("MyTaskExecutor-");
executor.initialize();
return executor;
}
// Everything else
}
然后,您将拥有如下所示的服务。
@Service
public class SomeServiceImplementation {
@Async("myTaskExecutor")
public void asyncMethod() {
// Do something
}
// Everything else
}
现在,扩展@bastiat 答案,测试将如下所示。
@Autowired
private SomeService someService;
@Autowired
private ThreadPoolTaskExecutor myTaskExecutor;
@Test
public void testAsyncMethod() {
Foo testData = prepareTestData();
this.someService.asyncMethod(testData);
this.myTaskExecutor.getThreadPoolExecutor().awaitTermination(1, TimeUnit.SECONDS);
this.verifyResults();
// Everything else
}
另外,我有一个与问题无关的小建议。我不会将 @Transactional
注释添加到 服务,只会添加到 DAO/存储库。除非您需要将其添加到必须是原子性的特定服务方法中。