57

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

1
  • 1
    the nice solution How do I write.. you mention above is a practicable one, expose asyncMethod result type as Future<Void>, 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<Void> f = someService.asyncMethod(testData); f.get(); verifyResults(); Commented Nov 19, 2019 at 16:33

6 Answers 6

42

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.
}
Sign up to request clarification or add additional context in comments.

1 Comment

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
31

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));

1 Comment

I used both of these answers. Worked great! Which I was nervous about because my test actually kicks off multiple async method calls.
27

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();
    }

3 Comments

if you have more than one test in the same spring context, then you have to take care about restarting your executor, right?
Matthias, probably yes, but I can't remember and can't check it as I already changed my job :(
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.
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.

4 Comments

alternatively you can use CompletableFuture::get
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 You can specify your own executor, see stackoverflow.com/a/61292577/3255540
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.
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.

Comments

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
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.