如何使用JUnit测试触发异步进程的方法?
我不知道如何让我的测试等待流程结束(它不是一个确切的单元测试,它更像一个集成测试,因为它涉及到几个类,而不仅仅是一个)。
如何使用JUnit测试触发异步进程的方法?
我不知道如何让我的测试等待流程结束(它不是一个确切的单元测试,它更像一个集成测试,因为它涉及到几个类,而不仅仅是一个)。
当前回答
JUnit 5有断言。assertTimeout(Duration, Executable)/ asserttimeoutpreempemptive()(请阅读每个的Javadoc以了解差异)和Mockito有verify(mock, timeout(毫秒).times(x))。
Assertions.assertTimeout(Duration.ofMillis(1000), () ->
myReactiveService.doSth().subscribe()
);
And:
Mockito.verify(myReactiveService,
timeout(1000).times(0)).doSth(); // cannot use never() here
管道中的超时可能是不确定的/脆弱的。所以要小心。
其他回答
启动进程并使用Future等待结果。
恕我直言,让单元测试创建或等待线程是一种糟糕的实践。您希望这些测试在瞬间运行。这就是为什么我想提出一个测试异步进程的两步方法。
测试您的异步进程是否正确提交。您可以模拟接受异步请求的对象,并确保提交的作业具有正确的属性,等等。 测试你的异步回调是否在做正确的事情。在这里,您可以模拟最初提交的作业,并假设它已正确初始化,并验证您的回调是否正确。
这里有很多答案,但一个简单的答案是创建一个完整的CompletableFuture并使用它:
CompletableFuture.completedFuture("donzo")
所以在我的测试中:
this.exactly(2).of(mockEventHubClientWrapper).sendASync(with(any(LinkedList.class)));
this.will(returnValue(new CompletableFuture<>().completedFuture("donzo")));
我只是确保所有这些东西都会被调用。如果你使用下面的代码,这个技巧是有效的:
CompletableFuture.allOf(calls.toArray(new CompletableFuture[0])).join();
它将压缩通过它,因为所有的CompletableFutures都完成了!
如果你想测试逻辑,不要异步测试。
例如,测试这段代码,它对异步方法的结果起作用。
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
}
}
在测试中模拟使用同步实现的依赖关系。单元测试是完全同步的,运行时间为150毫秒。
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")));
}
}
你不测试异步行为,但你可以测试逻辑是否正确。
对于测试异步方法,我发现一个非常有用的方法是在测试对象的构造函数中注入Executor实例。在生产中,执行器实例被配置为异步运行,而在测试中,它可以被模拟为同步运行。
所以假设我试图测试异步方法Foo#doAsync(Callback c),
class Foo {
private final Executor executor;
public Foo(Executor executor) {
this.executor = executor;
}
public void doAsync(Callback c) {
executor.execute(new Runnable() {
@Override public void run() {
// Do stuff here
c.onComplete(data);
}
});
}
}
在生产中,我会用Executors.newSingleThreadExecutor() Executor实例构造Foo,而在测试中,我可能会用执行以下操作的同步执行器构造它
class SynchronousExecutor implements Executor {
@Override public void execute(Runnable r) {
r.run();
}
}
现在异步方法的JUnit测试非常干净
@Test public void testDoAsync() {
Executor executor = new SynchronousExecutor();
Foo objectToTest = new Foo(executor);
Callback callback = mock(Callback.class);
objectToTest.doAsync(callback);
// Verify that Callback#onComplete was called using Mockito.
verify(callback).onComplete(any(Data.class));
// Assert that we got back the data that we expected.
assertEquals(expectedData, callback.getData());
}