How to use JUnit to test asynchronous processes

前端 未结 18 1592
小蘑菇
小蘑菇 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:54

    It's worth mentioning that there is very useful chapter Testing Concurrent Programs in Concurrency in Practice which describes some unit testing approaches and gives solutions for issues.

    0 讨论(0)
  • 2020-11-29 15:54

    This is what I'm using nowadays if the test result is produced asynchronously.

    public class TestUtil {
    
        public static <R> R await(Consumer<CompletableFuture<R>> completer) {
            return await(20, TimeUnit.SECONDS, completer);
        }
    
        public static <R> R await(int time, TimeUnit unit, Consumer<CompletableFuture<R>> completer) {
            CompletableFuture<R> f = new CompletableFuture<>();
            completer.accept(f);
            try {
                return f.get(time, unit);
            } catch (InterruptedException | TimeoutException e) {
                throw new RuntimeException("Future timed out", e);
            } catch (ExecutionException e) {
                throw new RuntimeException("Future failed", e.getCause());
            }
        }
    }
    

    Using static imports, the test reads kinda nice. (note, in this example I'm starting a thread to illustrate the idea)

        @Test
        public void testAsync() {
            String result = await(f -> {
                new Thread(() -> f.complete("My Result")).start();
            });
            assertEquals("My Result", result);
        }
    

    If f.complete isn't called, the test will fail after a timeout. You can also use f.completeExceptionally to fail early.

    0 讨论(0)
  • 2020-11-29 15:54

    There are many answers here but a simple one is to just create a completed CompletableFuture and use it:

    CompletableFuture.completedFuture("donzo")
    

    So in my test:

    this.exactly(2).of(mockEventHubClientWrapper).sendASync(with(any(LinkedList.class)));
    this.will(returnValue(new CompletableFuture<>().completedFuture("donzo")));
    

    I am just making sure all of this stuff gets called anyway. This technique works if you are using this code:

    CompletableFuture.allOf(calls.toArray(new CompletableFuture[0])).join();
    

    It will zip right through it as all the CompletableFutures are finished!

    0 讨论(0)
  • 2020-11-29 15:54

    If you want to test the logic just don´t test it asynchronously.

    For example to test this code which works on results of an asynchronous method.

    public class Example {
        private Dependency dependency;
    
        public Example(Dependency dependency) {
            this.dependency = dependency;            
        }
    
        public CompletableFuture<String> someAsyncMethod(){
            return dependency.asyncMethod()
                    .handle((r,ex) -> {
                        if(ex != null) {
                            return "got exception";
                        } else {
                            return r.toString();
                        }
                    });
        }
    }
    
    public class Dependency {
        public CompletableFuture<Integer> asyncMethod() {
            // do some async stuff       
        }
    }
    

    In the test mock the dependency with synchronous implementation. The unit test is completely synchronous and runs in 150ms.

    public class DependencyTest {
        private Example sut;
        private Dependency dependency;
    
        public void setup() {
            dependency = Mockito.mock(Dependency.class);;
            sut = new Example(dependency);
        }
    
        @Test public void success() throws InterruptedException, ExecutionException {
            when(dependency.asyncMethod()).thenReturn(CompletableFuture.completedFuture(5));
    
            // When
            CompletableFuture<String> result = sut.someAsyncMethod();
    
            // Then
            assertThat(result.isCompletedExceptionally(), is(equalTo(false)));
            String value = result.get();
            assertThat(value, is(equalTo("5")));
        }
    
        @Test public void failed() throws InterruptedException, ExecutionException {
            // Given
            CompletableFuture<Integer> c = new CompletableFuture<Integer>();
            c.completeExceptionally(new RuntimeException("failed"));
            when(dependency.asyncMethod()).thenReturn(c);
    
            // When
            CompletableFuture<String> result = sut.someAsyncMethod();
    
            // Then
            assertThat(result.isCompletedExceptionally(), is(equalTo(false)));
            String value = result.get();
            assertThat(value, is(equalTo("got exception")));
        }
    }
    

    You don´t test the async behaviour but you can test if the logic is correct.

    0 讨论(0)
  • 2020-11-29 15:57

    IMHO it's bad practice to have unit tests create or wait on threads, etc. You'd like these tests to run in split seconds. That's why I'd like to propose a 2-step approach to testing async processes.

    1. Test that your async process is submitted properly. You can mock the object that accepts your async requests and make sure that the submitted job has correct properties, etc.
    2. Test that your async callbacks are doing the right things. Here you can mock out the originally submitted job and assume it's initialized properly and verify that your callbacks are correct.
    0 讨论(0)
  • 2020-11-29 15:59

    JUnit 5 has Assertions.assertTimeout(Duration, Executable)/assertTimeoutPreemptively()(please read Javadoc of each to understand the difference) and Mockito has verify(mock, timeout(millisecs).times(x)).

    Assertions.assertTimeout(Duration.ofMillis(1000), () -> 
        myReactiveService.doSth().subscribe()
    );
    

    And:

    Mockito.verify(myReactiveService, 
        timeout(1000).times(0)).doSth(); // cannot use never() here
    

    Timeout may be nondeterministic/fragile in pipelines. So be careful.

    0 讨论(0)
提交回复
热议问题