Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JUnit-testing a Spring @Async void service method

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:

  • as SomeService.asyncMethod(..) is annotated with @Async and
  • as the SpringJUnit4ClassRunner adheres to the @Async semantics

the 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<?>.

like image 340
Abdull Avatar asked Feb 24 '17 12:02

Abdull


People also ask

What will happen if you specify @async over a public method of a Spring bean?

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.

Can we use @async on private method?

Never use @Async on top of a private method. In runtime, it will not able to create a proxy and, therefore, not work.

What does @async annotation do in Spring boot?

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.


2 Answers

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. } 
like image 141
Abdull Avatar answered Sep 28 '22 00:09

Abdull


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)); 
like image 28
jhyot Avatar answered Sep 28 '22 01:09

jhyot