JUnit 测试 Spring @Async void 服务方法
JUnit-testing a Spring @Async void service method
我有一个 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)
returns void
,而不是 Future<?>
.
要遵守@Async
语义,,例如
@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 测试 class 中,我明确激活 "non-async" 配置文件以相互排除异步行为:
@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.
}
如果您正在使用 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 功能更强大,它有很多选项可以处理异步断言。示例:
someAsyncCall();
await().atMost(5, SECONDS)
.untilAsserted(() -> assertThat(userRepo.size()).isEqualTo(1));
如果您的方法 returns CompletableFuture
使用 join
方法 - documentation CompletableFuture::join。
此方法等待异步方法完成并 returns 结果。任何遇到的异常都会在主线程中重新抛出。
我通过注射完成了
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();
}
只是为了扩展@bastiat 的答案,在我看来这应该被认为是正确的,如果您正在与多个执行者一起工作,您还应该指定 TaskExecutor
。所以你需要注入你希望等待的正确的。所以,假设我们有以下 配置 class.
@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/repository。除非你需要将它添加到必须是 atomic.
的特定服务方法
以上解决方案的补充:
@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
}
我有一个 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)
returns void
,而不是 Future<?>
.
要遵守@Async
语义,
@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 测试 class 中,我明确激活 "non-async" 配置文件以相互排除异步行为:
@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.
}
如果您正在使用 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 功能更强大,它有很多选项可以处理异步断言。示例:
someAsyncCall();
await().atMost(5, SECONDS)
.untilAsserted(() -> assertThat(userRepo.size()).isEqualTo(1));
如果您的方法 returns CompletableFuture
使用 join
方法 - documentation CompletableFuture::join。
此方法等待异步方法完成并 returns 结果。任何遇到的异常都会在主线程中重新抛出。
我通过注射完成了 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();
}
只是为了扩展@bastiat 的答案,在我看来这应该被认为是正确的,如果您正在与多个执行者一起工作,您还应该指定 TaskExecutor
。所以你需要注入你希望等待的正确的。所以,假设我们有以下 配置 class.
@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/repository。除非你需要将它添加到必须是 atomic.
以上解决方案的补充:
@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
}