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<?>
.
Simply put, annotating a method of a bean with @Async will make it execute in a separate thread. In other words, the caller will not wait for the completion of the called method. One interesting aspect in Spring is that the event support in the framework also has support for async processing if necessary.
Never use @Async on top of a private method. In runtime, it will not able to create a proxy and, therefore, not work.
The @EnableAsync annotation switches on Spring's ability to run @Async methods in a background thread pool. This class also customizes the Executor by defining a new bean. Here, the method is named taskExecutor , since this is the specific method name for which Spring searches.
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. }
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));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With