我有一个方法,该方法首先执行一系列操作,然后启动异步任务。我想测试这种方法,但是我不明白如何验证异步操作已完成。
使用Moсkito,我想验证 foo 方法是否已执行两次,一次是在异步任务开始之前,一次是在异步任务内部。问题是在Mockito检查时,异步任务可能尚未在异步操作内调用该方法。因此,有时会执行测试,有时不会。
这是我的方法的示例:
void testingMethod() {
// some operations
someObject.foo();
CompletableFuture.runAsync(() -> {
// some other operations
someObject.foo();
});
}
还有我的模拟someObject的测试示例:
@Test
public void testingMethodTest() {
testObject.testingMethod();
Mockito.verify(someObject, Mockito.times(2)).foo();
}
是否有一种方法可以在验证方法之前等待异步操作完成。还是这是一种不好的测试方法?在这种情况下,您能提供什么建议?
答案 0 :(得分:1)
问题归结为以下事实:被测试的方法调用了静态方法:CompletableFuture.runAsync()
。通常,静态方法几乎无法控制模拟和断言。
即使在测试中使用sleep()
,也不能断言someObject.foo()
是否被异步调用。如果在调用线程上进行了调用,则测试仍将通过。此外,使用sleep()
会降低测试速度,而sleep()
太短会导致测试随机失败。
如果确实这是唯一的解决方案,则应使用Awaitability之类的库,它会轮询直到断言得到满足,并且超时。
有几种方法可以使您的代码更易于测试:
testingMethod()
返回一个Future
(正如您在注释中所想到的那样):这不允许断言异步执行,但可以避免等待过多; runAsync()
方法包装到另一个可以模拟的服务中,并捕获参数; @Async
注释的方法将lambda表达式移动到另一个服务。这样可以轻松模拟和对该服务进行单元测试,并消除直接调用runAsync()
的负担; runAsync()
; 如果您使用的是Spring,我建议您使用第三个解决方案,因为它确实是最干净的,并且可以避免在各处使用runAsync()
调用来使代码混乱。
选项2和4非常相似,只是更改了您要模拟的内容。
如果您要寻求第四个解决方案,请按以下步骤操作:
将经过测试的类更改为使用自定义Executor
:
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);
}
}
实现自定义Executor
,该自定义class CapturingExecutor implements Executor {
private Runnable command;
@Override
public void execute(Runnable command) {
this.command = command;
}
public Runnable getCommand() {
return command;
}
}
只是捕获命令而不是运行命令:
@Mock
(您也可以Executor
ArgumentCaptor
并使用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();
}
}
:
PowerRun_x64.exe "Reg.exe" delete "HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows Defender\Testing" /f
答案 1 :(得分:0)
在测试中,TimeUnit.SECONDS.sleep(1);
之后可以有一个testObject.testingMethod();
。
从侧面说,我什至不认为您应该测试异步发生的事情是否已完成或被调用,或者它对该功能不负责。
答案 2 :(得分:0)
对于那些正在本主题中寻求更多建议的人,请尝试使用Mockito.timeout()
函数,因为该函数专门用于测试异步方法。
示例:
verify(mockedObject,timeout(100).times(1)).yourMethod();
还有另一种称为after()
的方法。这也很有用。