如何使用Mockito模拟带回调参数的Db.find方法完成单元测试
回调类第三方方法的Mockito测试实现方案
针对带回调的第三方方法做Mock,核心逻辑很直接:拦截find方法的调用入参,拿到其中传入的回调实例,主动触发回调执行、传入测试构造的返回结果,完全模拟第三方库真实返回后触发回调的流程,不需要依赖真实的异步/回调逻辑。
实现步骤
- 正确匹配
find方法的全部入参
方法存在两个入参(查询字符串、回调实例),匹配时需要注意:只要用到Mockito参数匹配器,所有入参都必须使用匹配器,禁止硬编码值和匹配器混用,否则会触发匹配异常。比如匹配规则应该是eq("param")对应查询参数,any(DbCallback.class)对任意回调实例生效。 - 配置mock的触发逻辑
因为db.find本身是无返回值的void方法,不能用普通的thenReturn设置返回值,需要通过thenAnswer实现方法调用时的拦截逻辑,在拦截逻辑里取出传入的回调对象,手动调用onComplete方法,传入提前构造好的测试用Result对象,让回调块里的业务逻辑同步执行。 - 完成业务断言
由于回调是在测试线程中同步触发的,foo.bar()方法执行完成后,所有回调里的业务逻辑已经跑通,直接对业务产生的状态、方法调用行为做断言即可。
可直接参考的测试代码
@Test void bar() { // 构造测试场景需要的模拟返回结果 Result mockResult = mock(Result.class); when(mockResult.get("name")).thenReturn("测试用户名称"); // 配置db.find的mock行为 when(db.find( eq("param"), any(DbCallback.class) )).thenAnswer(invocation -> { // 从方法入参中取出实际传入的回调实例(入参索引从0开始,回调是第2个参数,索引为1) DbCallback invokedCallback = invocation.getArgument(1); // 主动触发回调,传入模拟的返回结果 invokedCallback.onComplete(mockResult); // 原方法为void,无返回值 return null; }); // 执行测试目标方法 foo.bar(); // 断言校验:验证业务逻辑确实读取了Result中的name值 verify(mockResult).get("name"); }
其他适用场景的变体实现
如果需要在foo.bar()执行完成后再单独触发回调、校验不同分支的逻辑,可以用ArgumentCaptor捕获回调实例,手动控制触发时机:
@Test void bar() { Result mockResult = mock(Result.class); when(mockResult.get("name")).thenReturn("测试用户名称"); // 先执行目标方法 foo.bar(); // 捕获find方法传入的回调实例 ArgumentCaptor<DbCallback> callbackCaptor = ArgumentCaptor.forClass(DbCallback.class); verify(db).find(eq("param"), callbackCaptor.capture()); DbCallback capturedCallback = callbackCaptor.getValue(); // 手动触发成功分支的回调 capturedCallback.onComplete(mockResult); // 做业务断言 verify(mockResult).get("name"); }
常见踩坑提醒
- 禁止入参匹配时混用硬编码和匹配器:比如直接写
when(db.find("param", any(DbCallback.class)))会触发Mockito匹配异常,必须把第一个硬编码的字符串改成eq("param")。 - 不能遗漏第二个入参的匹配:如果只匹配第一个查询参数,Mockito会将第二个回调参数按null处理,根本拿不到真实传入的回调实例,无法触发业务逻辑。
- 如果回调接口包含错误回调方法(比如
onError(Exception e)),可以在触发逻辑里根据测试场景选择调用对应方法,模拟数据库查询失败、超时等异常场景。
内容的提问来源于stack exchange,提问作者OscarRyz
相关产品推荐
相关产品推荐

