如果被测方法是异步调用,则 withTimes 不生效
oriming opened this issue · 4 comments
oriming commented
- 被测方法:
@Service
public class LayoutServiceImpl implements LayoutService {
// 被测方法
@Override
@Async(ThreadPoolConstant.XXX_POOL_NAME)
public void asyncParallelExec() {
asyncExecImmediately();
}
@Override
@Async(ThreadPoolConstant.XXX_POOL_NAME)
public void asyncExecImmediately() {
System.out.println("asyncExecImmediately");
}
}
- 测试方法:
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class LayoutServiceImplTest extends BaseFakerTest {
@Autowired
LayoutServiceImpl service;
public static class Mock {
@MockInvoke(targetClass = LayoutServiceImpl.class)
public void asyncExecImmediately() {
log.warn("mock invoke [asyncExecImmediately] of LayoutServiceImpl");
}
}
@Test
public void asyncParallelExec() {
assertDoesNotThrow(() -> service.asyncParallelExec());
verifyInvoked("asyncExecImmediately").withTimes(1);
}
}
- Console 输出
2022-11-24 10:34:47.379 WARN 18294 [] --- [k-exec-thread-1] .s.a.s.s.i.LayoutServiceImplTest : mock invoke [asyncExecImmediately] of LayoutServiceImpl
com.alibaba.testable.core.error.VerifyFailedError:
Expected times: 1
Actual times: 0
linfan commented
问题收到,感谢详细的代码示例,我先尝试复现一下看看
linfan commented
这个问题确实有点神奇... 排查了好久最后发现原因其实就在眼前🥲
由于@Async
的作用,实际Mock方法的调用其实是发生在verifyInvoked()
之后的,所以在做withTimes(1)
断言的时候,这个Mock方法的调用次数确实依然是0:
@Test
public void asyncParallelExec() {
assertDoesNotThrow(() -> service.asyncParallelExec()); // 这行先执行
verifyInvoked("asyncExecImmediately").withTimes(1); // 然后开始做断言
// 在这之后,asyncParallelExec()方法里面的asyncExecImmediately()调用才发生
}
只需要在verifyInvoked()
和Mock方法里同时加上断点执行测试就能发现其中的“奥妙”。
解决办法也很简单,在verifyInvoked("asyncExecImmediately").withTimes(1);
前面加上一行Thread.sleep(1000);
,测试通过🤣
linfan commented
顺带一提,对于在方法内显示使用了异步回调的调用(比如Mock在CompletableFuture.supplyAsync()
回调里的方法调用),还需要开启Testable封装的transmittable-thread-local库针对异步线程的字节码增强逻辑,具体方法见Mock线程池内的调用,这种情况很少遇到,通常可以忽略。