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

Jest中jest.fn()传入后预期被调用 实际显示0次调用问题排查

编辑注2:可查看下方评论

问题描述

为Express中间件函数编写单元测试时,.toHaveBeenCalled()断言始终返回不符合预期的结果:预期mock函数被调用1次,实际统计为0次。测试中通过手动构造mock对象模拟Express Response传入被测函数,代码如下:

被测代码 index.ts

const doSomething = async (req, res) => {
  // ....
  
  axios(options)
    .then((response) => {
        const data = response.data;
        // ...
        res.status(200);
        res.send();
    })
  // ....
}

测试代码 index.test.ts

const sampleResponse = {
  status: jest.fn(),
  send: jest.fn(),
  json: jest.fn(),
} as unknown as Response;

// ...

await doSomething(sampleRequest, sampleResponse);
expect(sampleResponse.status).toHaveBeenCalled();

Jest覆盖率报告显示res.status(200)与res.send()所在代码行已被覆盖。
查阅资料后怀疑问题和测试执行过程中mock函数实例独立、引用不一致有关,需要确认是否属于该类问题,同时被测函数无返回值,是否需要调整测试方向。


编辑补充

后续调整方向为测试函数副作用,仍遇到mock函数0次调用的问题,怀疑是相同的引用/实例问题导致,更新后代码如下:

更新后被测代码 index.ts

import Foo from './foo.ts';

const doSomething = async (req, res) => {
  // ....

  axios(options)
    .then((response) => {
        const data = response.data;
        // ...
        // getInstance()会查找实例,若实例不存在则创建新实例
        Foo.getInstance().doFooThings();
    })
  // ....
}

更新后测试代码 index.test.ts

import Foo from './foo.ts'

// ...
const fooMock = jest.spyOn(Foo.prototype,'doFooThings').mockImplementation(() => Promise.resolve(console.log('hi')));
await doSomething(req, res);
expect(fooMock).toHaveBeenCalled(); // 仍显示0次调用,但控制台已打印出'hi'
// ...

问题根因

和mock实例引用不一致无关,核心是异步逻辑未等待完成就执行了断言:

  • doSomething虽然标记为async,但内部的axios(options).then(...)链式调用既没有加await,也没有作为返回值返回,函数会在axios请求发出后直接返回,不会等待then回调中的逻辑执行。
  • 测试代码中的await doSomething(...)只会等待doSomething函数体内同步逻辑执行完毕,此时axios返回的Promise还处于pending状态,then回调里的res.status、doFooThings都还没执行,断言自然统计到0次调用。
  • 控制台能打印'hi'、覆盖率能覆盖对应代码行,是因为Jest默认会等待所有待执行微任务清空后才退出测试进程:断言执行完成后,axios的Promise才决议,then回调才开始执行,此时断言早已跑完,自然捕获不到调用记录。
  • 补充一个代码笔误:原测试代码中jest.spyOn(foo.prototype,'doFooThings')的类名首字母小写,和导入的Foo大小写不一致,也会导致spy挂载失败。
修复方案

两种方案二选一即可:

  • 方案1:调整业务代码,让外层函数能等待axios链路执行完成
    const doSomething = async (req, res) => {
      // ...
      // 加await或者return,让doSomething返回的Promise可以感知到axios链路的执行状态
      return axios(options)
        .then((response) => {
          const data = response.data;
          // ...
          res.status(200);
          res.send();
        })
      // ...
    }
    
  • 方案2:不修改业务代码的前提下,在测试中等待所有微任务排空后再执行断言
    await doSomething(sampleRequest, sampleResponse);
    // 等待所有待处理的Promise微任务执行完成
    await new Promise(process.nextTick);
    // 执行断言
    expect(sampleResponse.status).toHaveBeenCalledWith(200);
    expect(fooMock).toHaveBeenCalled();
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 05:09:33