Java 在单元测试中,如何在异步操作中测试方法调用
我有一个方法,它首先执行一系列操作,然后启动一个异步任务。我想测试这个方法,但我不明白如何验证异步操作已经完成 使用MoСkito,我想验证foo方法执行了2次,一次在异步任务开始之前,一次在异步任务内部。问题是,在Mockito检查时,异步任务可能还没有在异步操作中调用该方法。因此,有时进行试验,有时不进行试验 这是我的方法的示例:Java 在单元测试中,如何在异步操作中测试方法调用,java,asynchronous,mockito,junit5,completable-future,Java,Asynchronous,Mockito,Junit5,Completable Future,我有一个方法,它首先执行一系列操作,然后启动一个异步任务。我想测试这个方法,但我不明白如何验证异步操作已经完成 使用MoСkito,我想验证foo方法执行了2次,一次在异步任务开始之前,一次在异步任务内部。问题是,在Mockito检查时,异步任务可能还没有在异步操作中调用该方法。因此,有时进行试验,有时不进行试验 这是我的方法的示例: void testingMethod() { // some operations someObject.foo(); Completab
void testingMethod() {
// some operations
someObject.foo();
CompletableFuture.runAsync(() -> {
// some other operations
someObject.foo();
});
}
还有我的测试示例,其中模拟了某个对象:
@Test
public void testingMethodTest() {
testObject.testingMethod();
Mockito.verify(someObject, Mockito.times(2)).foo();
}
是否有一种方法可以在验证方法之前等待异步操作完成。或者这是一种不好的测试方法,在这种情况下,您可以提供什么建议?您可以使用
时间单位.SECONDS.sleep(1)代码>在testObject.testingMethod()之后代码>在您的测试中
另一方面,我甚至不认为您应该测试异步发生的事情是否已完成或被调用,这不是此函数的责任。您可以有一个时间单位.SECONDS.sleep(1)代码>在testObject.testingMethod()之后代码>在您的测试中
另一方面,我甚至不认为您应该测试异步发生的事情是否已完成或调用,这不是此函数的责任。问题归结为被测试的方法调用静态方法:CompletableFuture.runAsync()
。静态方法通常很少控制模拟和断言
即使在测试中使用sleep()
,也不能断言是否异步调用了someObject.foo()
。如果调用是在调用线程上进行的,则测试仍将通过。此外,使用sleep()
会降低测试速度,而sleep()
太短会导致测试随机失败
如果这似乎真的是唯一的解决方案,那么应该使用这样的库进行轮询,直到满足断言为止,并有一个超时
有几种方法可以使代码更易于测试:
MaketestingMethod()
返回一个Future
(如您在评论中所想):这不允许断言异步执行,但可以避免等待太久李>
将runAsync()
方法包装到另一个可以模拟的服务中,并捕获参数李>
如果您使用的是Spring,请将lambda表达式移动到另一个服务中,使用带有@Async
注释的方法。这允许轻松地模拟和单元测试该服务,并消除了直接调用runAsync()
的负担李>
使用自定义执行器,并将其传递给runAsync()
李>
如果您使用的是Spring,我建议使用第三种解决方案,因为它确实是最干净的,并且避免了到处调用runAsync()
而使代码混乱
选项2和4非常相似,它只是改变了你必须模仿的东西
如果您选择第四种解决方案,以下是您的方法:
将测试的类更改为使用自定义执行器:
class TestedObject {
private SomeObject someObject;
private Executor executor;
public TestedObject(SomeObject someObject, Executor executor) {
this.someObject = someObject;
this.executor = executor;
}
void testingMethod() {
// some operations
someObject.foo();
CompletableFuture.runAsync(() -> {
// some other operations
someObject.foo();
}, executor);
}
}
实现一个自定义的执行器,它只捕获命令而不是运行命令:
class CapturingExecutor implements Executor {
private Runnable command;
@Override
public void execute(Runnable command) {
this.command = command;
}
public Runnable getCommand() {
return command;
}
}
(您也可以使用@Mock
执行器和参数捕获器
,但我认为这种方法更干净)
在测试中使用CapturingExecutor
:
@RunWith(MockitoJUnitRunner.class)
public class TestedObjectTest {
@Mock
private SomeObject someObject;
private CapturingExecutor executor;
private TestedObject testObject;
@Before
public void before() {
executor = new CapturingExecutor();
testObject = new TestedObject(someObject, executor);
}
@Test
public void testingMethodTest() {
testObject.testingMethod();
verify(someObject).foo();
// make sure that we actually captured some command
assertNotNull(executor.getCommand());
// now actually run the command and check that it does what it is expected to do
executor.getCommand().run();
// Mockito still counts the previous call, hence the times(2).
// Not relevant if the lambda actually calls a different method.
verify(someObject, times(2)).foo();
}
}
问题归结为被测试方法调用静态方法:CompletableFuture.runAsync()
。静态方法通常很少控制模拟和断言
即使在测试中使用sleep()
,也不能断言是否异步调用了someObject.foo()
。如果调用是在调用线程上进行的,则测试仍将通过。此外,使用sleep()
会降低测试速度,而sleep()
太短会导致测试随机失败
如果这似乎真的是唯一的解决方案,那么应该使用这样的库进行轮询,直到满足断言为止,并有一个超时
有几种方法可以使代码更易于测试:
MaketestingMethod()
返回一个Future
(如您在评论中所想):这不允许断言异步执行,但可以避免等待太久李>
将runAsync()
方法包装到另一个可以模拟的服务中,并捕获参数李>
如果您使用的是Spring,请将lambda表达式移动到另一个服务中,使用带有@Async
注释的方法。这允许轻松地模拟和单元测试该服务,并消除了直接调用runAsync()
的负担李>
使用自定义执行器,并将其传递给runAsync()
李>
如果您使用的是Spring,我建议使用第三种解决方案,因为它确实是最干净的,并且避免了到处调用runAsync()
而使代码混乱
选项2和4非常相似,它只是改变了你必须模仿的东西
如果您选择第四种解决方案,以下是您的方法:
将测试的类更改为使用自定义执行器:
class TestedObject {
private SomeObject someObject;
private Executor executor;
public TestedObject(SomeObject someObject, Executor executor) {
this.someObject = someObject;
this.executor = executor;
}
void testingMethod() {
// some operations
someObject.foo();
CompletableFuture.runAsync(() -> {
// some other operations
someObject.foo();
}, executor);
}
}
实现一个自定义的执行器,它只捕获命令而不是运行命令:
class CapturingExecutor implements Executor {
private Runnable command;
@Override
public void execute(Runnable command) {
this.command = command;
}
public Runnable getCommand() {
return command;
}
}
(您也可以使用@Mock
执行器和参数捕获器
,但我认为这种方法更干净)
在测试中使用CapturingExecutor
:
@RunWith(MockitoJUnitRunner.class)
public class TestedObjectTest {
@Mock
private SomeObject someObject;
private CapturingExecutor executor;
private TestedObject testObject;
@Before
public void before() {
executor = new CapturingExecutor();
testObject = new TestedObject(someObject, executor);
}
@Test
public void testingMethodTest() {
testObject.testingMethod();
verify(someObject).foo();
// make sure that we actually captured some command
assertNotNull(executor.getCommand());
// now actually run the command and check that it does what it is expected to do
executor.getCommand().run();
// Mockito still counts the previous call, hence the times(2).
// Not relevant if the lambda actually calls a different method.
verify(someObject, times(2)).foo();
}
}
对于那些你正在寻找的人