Reputation: 27832
I have a Spring service:
@Service
@Transactional
public class SomeService {
@Async
public void asyncMethod(Foo foo) {
// processing takes significant time
}
}
And I have an integration test for this 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.
}
Here is the problem:
SomeService.asyncMethod(..)
is annotated with @Async
andSpringJUnit4ClassRunner
adheres to the @Async
semanticsthe testAsyncMethod
thread will fork the call someService.asyncMethod(testData)
into its own worker thread, then directly continue executing verifyResults()
, possibly before the previous worker thread has finished its work.
How can I wait for someService.asyncMethod(testData)
's completion before verifying the results? Notice that the solutions to How do I write a unit test to verify async behavior using Spring 4 and annotations? don't apply here, as someService.asyncMethod(testData)
returns void
, not a Future<?>
.
Upvotes: 55
Views: 64011
Reputation: 3955
If you are using Mockito (directly or via Spring testing support @MockBean
), it has a verification mode with a timeout exactly for this case:
https://static.javadoc.io/org.mockito/mockito-core/2.10.0/org/mockito/Mockito.html#22
someAsyncCall();
verify(mock, timeout(100)).someMethod();
Much more capable is the great library Awaitility, which has many options how to handle async assertions. Example:
someAsyncCall();
await().atMost(5, SECONDS)
.untilAsserted(() -> assertThat(userRepo.size()).isEqualTo(1));
Upvotes: 28
Reputation: 13090
Just addition to the above solutions:
@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
}
Upvotes: 0
Reputation: 5097
Just to extend the answer by @bastiat, which in my opinion should be considered the correct one, you should also specified the TaskExecutor
, if you are working with multiple executors. So you would need to inject the correct one that you wish to wait for. So, let's imagine we have the following configuration 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
}
Then, you would have a service that would look like the following one.
@Service
public class SomeServiceImplementation {
@Async("myTaskExecutor")
public void asyncMethod() {
// Do something
}
// Everything else
}
Now, extending on @bastiat answer, the test would look like the following one.
@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
}
Also, I have a minor recommendation that has nothing to do with the question. I wouldn't add the @Transactional
annotation to a service, only to the DAO/repository. Unless you need to add it to a specific service method that must be atomic.
Upvotes: 3
Reputation: 2071
I have done by injecting ThreadPoolTaskExecutor
and then
executor.getThreadPoolExecutor().awaitTermination(1, TimeUnit.SECONDS);
before verifying results, it as below:
@Autowired
private ThreadPoolTaskExecutor executor;
@Test
public void testAsyncMethod() {
Foo testData = prepareTestData();
someService.asyncMethod(testData);
executor.getThreadPoolExecutor().awaitTermination(1, TimeUnit.SECONDS);
verifyResults();
}
Upvotes: 27
Reputation: 805
In case your method returns CompletableFuture
use join
method - documentation CompletableFuture::join.
This method waits for the async method to finish and returns the result. Any encountered exception is rethrown in the main thread.
Upvotes: 5
Reputation: 27832
For @Async
semantics to be adhered, some active @Configuration
class will have the @EnableAsync
annotation, e.g.
@Configuration
@EnableAsync
@EnableScheduling
public class AsyncConfiguration implements AsyncConfigurer {
//
}
To resolve my issue, I introduced a new Spring profile non-async
.
If the non-async
profile is not active, the AsyncConfiguration
is used:
@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
}
If the non-async profile is active, the NonAsyncConfiguration
is used:
@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
}
Now in the problematic JUnit test class, I explicitly activate the "non-async" profile in order to mutually exclude the async behavior:
@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.
}
Upvotes: 42