54

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

Community
  • 1
  • 1
Abdull
  • 26,371
  • 26
  • 130
  • 172
  • 1
    the nice solution [How do I write..](https://stackoverflow.com/q/20807232/923560) you mention above is a practicable one, expose *asyncMethod* result type as *Future*, and return **new AsyncResult<>(null);** at the last of *asyncMethod*; and in the test method, get the future from *asyncMethod* and wait for it, as like ```Future f = someService.asyncMethod(testData); f.get(); verifyResults();``` – Honwhy Wang Nov 19 '19 at 16:33

6 Answers6

39

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.
}
Community
  • 1
  • 1
Abdull
  • 26,371
  • 26
  • 130
  • 172
  • Interesting approach, but this only works if none of the spring configurations depend on a task executor. If the async tests are an issue I would suggest moving them to a separate surefire execution – mwhs Oct 28 '18 at 07:57
27

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));
jhyot
  • 3,733
  • 1
  • 27
  • 44
  • I used both of these answers. Worked great! Which I was nervous about because my test actually kicks off multiple async method calls. – cody.tv.weber Mar 08 '23 at 12:59
25

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();
    }
bastiat
  • 1,799
  • 2
  • 19
  • 38
  • 1
    if you have more than one test in the same spring context, then you have to take care about restarting your executor, right? – Matthias Wiedemann Jun 15 '21 at 15:17
  • Matthias, probably yes, but I can't remember and can't check it as I already changed my job :( – bastiat Jun 23 '21 at 11:03
  • This worked, but slowed down my tests waiting for the timeouts. So I have 5 tests, so the tests ran >5secs, which is really slow in testing world. So I'm treating this as a backup solution. – cody.tv.weber Mar 08 '23 at 12:58
5

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.

Jan Mares
  • 795
  • 10
  • 22
  • alternatively you can use CompletableFuture::get – Deekshith Anand Mar 15 '21 at 17:58
  • Apparently if you are using Spring Boot, using CompletableFuture or Runnable spins off your own threads which can mess with Spring Boots threads. So from my understanding, @Async uses Spring Boot for the threads which then is safe. If this is correct, then I'm kinda annoyed that Spring is so invasive that I am forced to use Spring solutions over vanilla Java solutions. – cody.tv.weber Mar 08 '23 at 13:04
  • @cody.tv.weber You can specify your own executor, see https://stackoverflow.com/a/61292577/3255540 – Jan Mares Mar 09 '23 at 10:50
  • Sure, and thank you for showing me this, Still, I would rather be able to use pure Java if I wanted to. Also, what is gonna happen whenever VirtualThreads come? Am I gonna have to use a Spring version for that as well? Not that big of a deal. Just annoying. – cody.tv.weber May 17 '23 at 19:14
3

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.

Alain Cruz
  • 4,757
  • 3
  • 25
  • 43
0

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
    }

Sarvar Nishonboyev
  • 12,262
  • 10
  • 69
  • 70