You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ScalaMock报错无法记录mock对象调用 期望是否已完成校验

根因分析

这个异常是ScalaMock生命周期与异步代码执行时序不匹配导致的,核心触发逻辑如下:

  • ScalaMock配合ScalaTest异步测试用例时,会在测试用例返回的Future完成后立即执行所有mock期望的校验,校验完成后mock对象会被锁定,不允许再记录任何方法调用,后续调用直接抛出该异常。
  • 业务代码存在游离异步任务的问题:getAccounts方法中,resultF.map触发的updateAccountsCache是独立的Future链,没有和方法最终返回的Future做绑定。也就是说主请求结果返回时,缓存更新的逻辑还在后台异步执行,并没有完成。
  • 测试用例拿到getAccounts返回的Future结果后,认为测试逻辑执行完毕,触发ScalaMock完成期望校验、锁定mock对象。此时后台才执行到updateAccountsCache里的upsertSyncDataLake、refreshCache等DAO调用,调用已经被锁定的mock就会抛出对应错误。
  • 错误日志可以直接佐证:异常是在打印「Failed to update the account cache」的逻辑里输出的,说明是主流程返回后,缓存更新的异步任务延迟执行才触发的错误。
修复方案

优先修复业务代码(推荐)

现有业务代码的游离异步任务本身就是生产隐患:缓存更新失败无统一感知、任务脱离链路监控易出现线程池泄露。需要将缓存更新逻辑绑定到主请求的Future链上,不要让它成为脱离返回值的后台任务,修改示例:

val resultF = UnfoldIO
  .getWhileNotEmpty(request = request, pageNumber = 1)
  .collectAndRecover(entity = entity, companyId = companyId)

// 绑定缓存更新逻辑到主链路,缓存更新失败不阻断主请求返回
val resultWithCache = resultF.flatMap { accounts =>
  val accountSeq = accounts.toSeq
  updateAccountsCache(
      ledgerId = ledgerId, 
      accounts = accountSeq, 
      codatAccounts = codatAccounts.toSeq
    )
    .map(_ => accountSeq)
    .recover(_ => accountSeq) // 对齐原有逻辑,缓存更新失败不影响主结果返回
}

resultWithCache.recoverWith { case _: Timeout =>
  accountCacheDAO.getAccounts(ledgerId = ledgerId)
}

改完后getAccounts返回的Future会等缓存更新逻辑执行完再完成,测试时ScalaMock会等所有mock调用都结束再做校验,不会再出现提前锁mock的问题。

测试侧临时适配(不推荐)

如果暂时不调整业务逻辑,可以在测试用例的最终校验前加固定时长的等待,等后台缓存更新任务执行完再结束测试,但这种方案不稳定,本质是掩盖业务代码的设计问题,不建议使用。

内容的提问来源于stack exchange,提问作者Artie Peshimam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 16:06:26