Niraj Sonawane
Niraj Sonawane

Reputation: 11105

How to unit test CompletableFuture.thenAccept() by avoiding manual sleep

How to avoid manual sleep in unit test. Suppose In below code the Process and notify takes around 5 seconds for processing. So in order to complete the processing, i have added sleep of 5 seconds.

public class ClassToTest {

    public ProcessService processService;
    public NotificationService notificationService;

    public ClassToTest(ProcessService pService ,NotificationService nService ) {
        this.notificationService=nService;
        this.processService = pService;
    }
    public CompletableFuture<Void> testMethod()
    {
          return CompletableFuture.supplyAsync(processService::process)
                        .thenAccept(notificationService::notify);
    }

}

is there any better way to handle this ?

 @Test
    public void comletableFutureThenAccept() {
         CompletableFuture<Void> thenAccept = 
          sleep(6);
          assertTrue(thenAccept.isDone());  
          verify(mocknotificationService, times(1)).notify(Mockito.anystring());
    }

Upvotes: 2

Views: 5525

Answers (1)

Holger
Holger

Reputation: 298469

Normally, you want to test whether an underlying operation completes with the intended result, has the intended side effect, or at least completes without throwing an exception. This can be achieved as easy as

@Test
public void comletableFutureThenAccept() {
      CompletableFuture<Void> future = someMethod();
      future.join();
      /* check for class under test to have the desired state */
}

join() will wait for the completion and return the result (which you can ignore in case of Void), throwing an exception if the future completed exceptionally.

If completing withing a certain time is actually part of the test, simply use

@Test(timeout = 5000)
public void comletableFutureThenAccept() {
      CompletableFuture<Void> future = someMethod();
      future.join();
      /* check for class under test to have the desired state */
}

In the unlikely case that you truly want to test for completion within the specified time only, i.e. do not care whether the operation threw an exception, you can use

@Test(timeout = 5000)
public void comletableFutureThenAccept() {
      CompletableFuture<Void> future = someMethod();
      future.exceptionally(t -> null).join();
}

This substitutes an exceptional completion with a null result, hence, join() won’t throw an exception. So only the timeout remains.

Java 9 allows another alternative, not using JUnit’s timeout.

@Test()
public void comletableFutureThenAccept() {
      CompletableFuture<Void> future = someMethod().orTimeout(5, TimeUnit.SECONDS);
      future.join();
      /* check for class under test to have the desired state */
}

This has the advantage of not failing if the operation completes in time but the subsequent verification takes longer.

Upvotes: 6

Related Questions