Avishai Yaniv
Avishai Yaniv

Reputation: 410

Testing CompletableFuture.allOf. join takes forever

I am trying to test a method that uses CompletableFuture.allOf.
I mock the futures so that they return the needed value when joined.
for some reason which I can't understand. join() of result takes forever.
any help will do!

Thanks in advance.

    @Mock private CompletableFuture<String> future1; // for sequence test
    @Mock private CompletableFuture<String> future2; // for sequence test
    @Mock private CompletableFuture<String> future3; // for sequence test

    private BBService bbServiceTest;

    @Test
    public void testSequence() {
        final String[] expectedResults = {"res1", "res2", "res3"};
        List<CompletableFuture<String>> futures = new Vector<>();

        when(future1.join()).thenReturn(expectedResults[0]);
        when(future2.join()).thenReturn(expectedResults[1]);
        when(future3.join()).thenReturn(expectedResults[2]);

        futures.add(future1);
        futures.add(future2);
        futures.add(future3);

        bbServiceTest.sequence(futures).join();
        Assert.assertTrue(future1.isDone());
        Assert.assertTrue(future2.isDone());
        Assert.assertTrue(future3.isDone());

//        for (int i = 0; i < actualResults.size(); i++) {
//            Assert.assertEquals(expectedResults[i], actualResults.get(i));
//        }
    }


sequence method inside BBService:

    public <T> CompletableFuture<List<T>> sequence(List<CompletableFuture<T>> futuresList) {
        return CompletableFuture
                .allOf(futuresList.toArray(new CompletableFuture[0])) // (1)
                .thenApply(v ->
                        futuresList.stream()
                                .map(CompletableFuture::join)
                                .collect(Collectors.toList())
                );
    }

Upvotes: 1

Views: 4528

Answers (1)

name not found
name not found

Reputation: 622

Thats because your futures never get completed. CompletableFuture.allOf returns a new CompletableFuture that is completed when all of the given CompletableFutures complete.

future1.complete(), future2.complete() and future3.complete() never get called.

CompletableFuture.join() returns the result if the future is completed. Because you mock the futures in your code they return the strings defined in expectedResults on a join even if they're not completed. But that's exactly the problem. They don't complete. That's why the test doesn't finish.

I adjusted your Test class a little to demonstrate how it works:

public class FutureTest {
    @Test
    public void testSequence1() {
        List<CompletableFuture<String>> futures = getFutures();
        CompletableFuture<Void> myCombinedFuture = sequence(futures);
        // here we complete each future
        futures.forEach(future -> future.complete("future-completed"));
        myCombinedFuture.join();

        // this works because we completed all futures before
        Assert.assertTrue(myCombinedFuture.isDone());
    }

    @Test
    public void testSequence2() {
        List<CompletableFuture<String>> futures = getFutures();
        CompletableFuture<Void> myCombinedFuture = sequence(futures);
        futures.get(0).complete("completed-future1");
        futures.get(1).complete("completed-future2");

        myCombinedFuture.join();
        // this does not work because future3 is not completed -> we wait forever
        Assert.assertTrue(myCombinedFuture.isDone());
    }

    @Test
    public void testSequence3() {
        List<CompletableFuture<String>> futures = getFutures();
        CompletableFuture<Void> myCombinedFuture = sequence(futures);
        futures.get(0).complete("completed-future1");
        futures.get(1).complete("completed-future2");

        // complete future 3 asynchronous after 2 seconds
        futures.get(2).completeAsync(() -> {
            // wait for 2 seconds
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            return "completed-future3";
        });

        myCombinedFuture.join();
        // after 2 seconds all futures are completed and the test will pass
        Assert.assertTrue(myCombinedFuture.isDone());
    }

    public CompletableFuture<Void> sequence(
            List<CompletableFuture<String>> futuresList) {
        return CompletableFuture.allOf(
            futuresList.toArray(new CompletableFuture[0]));
    }

    public List<CompletableFuture<String>> getFutures() {
        return List.of(
                new CompletableFuture<>(),
                new CompletableFuture<>(),
                new CompletableFuture<>()
        );
    }
}

Upvotes: 4

Related Questions