How to use JUnit to test asynchronous processes

前端 未结 18 1623
小蘑菇
小蘑菇 2020-11-29 15:06

How do you test methods that fire asynchronous processes with JUnit?

I don\'t know how to make my test wait for the process to end (it is not exactly a unit test, it

18条回答
  •  北海茫月
    2020-11-29 15:38

    Avoid testing with parallel threads whenever you can (which is most of the time). This will only make your tests flaky (sometimes pass, sometimes fail).

    Only when you need to call some other library / system, you might have to wait on other threads, in that case always use the Awaitility library instead of Thread.sleep().

    Never just call get() or join() in your tests, else your tests might run forever on your CI server in case the future never completes. Always assert isDone() first in your tests before calling get(). For CompletionStage, that is .toCompletableFuture().isDone().

    When you test a non-blocking method like this:

    public static CompletionStage createGreeting(CompletableFuture future) {
        return future.thenApply(result -> "Hello " + result);
    }
    

    then you should not just test the result by passing a completed Future in the test, you should also make sure that your method doSomething() does not block by calling join() or get(). This is important in particular if you use a non-blocking framework.

    To do that, test with a non-completed future that you set to completed manually:

    @Test
    public void testDoSomething() throws Exception {
        CompletableFuture innerFuture = new CompletableFuture<>();
        CompletableFuture futureResult = createGreeting(innerFuture).toCompletableFuture();
        assertFalse(futureResult.isDone());
    
        // this triggers the future to complete
        innerFuture.complete("world");
        assertTrue(futureResult.isDone());
    
        // futher asserts about fooResult here
        assertEquals(futureResult.get(), "Hello world");
    }
    

    That way, if you add future.join() to doSomething(), the test will fail.

    If your Service uses an ExecutorService such as in thenApplyAsync(..., executorService), then in your tests inject a single-threaded ExecutorService, such as the one from guava:

    ExecutorService executorService = Executors.newSingleThreadExecutor();
    

    If your code uses the forkJoinPool such as thenApplyAsync(...), rewrite the code to use an ExecutorService (there are many good reasons), or use Awaitility.

    To shorten the example, I made BarService a method argument implemented as a Java8 lambda in the test, typically it would be an injected reference that you would mock.

提交回复
热议问题