alibaba/testable-mock

如果被测方法是异步调用,则 withTimes 不生效

oriming opened this issue · 4 comments

  1. 被测方法:
@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");
    }
}
  1. 测试方法:
@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);
    }
}
  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

问题收到,感谢详细的代码示例,我先尝试复现一下看看

这个问题确实有点神奇... 排查了好久最后发现原因其实就在眼前🥲

由于@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);,测试通过🤣

顺带一提,对于在方法内显示使用了异步回调的调用(比如Mock在CompletableFuture.supplyAsync()回调里的方法调用),还需要开启Testable封装的transmittable-thread-local库针对异步线程的字节码增强逻辑,具体方法见Mock线程池内的调用,这种情况很少遇到,通常可以忽略。

@linfan 原来如此,感谢😂