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

Jest中spyOn配合mockImplementation抛错未触发catch的解决方法

问题根因

测试失败由三个问题共同导致:

  • Mock引用不生效:如果index.ts中是通过import { promisify } from 'util'具名导入promisify,测试文件里通过import * as util from 'util'后spyOnutil.promisify,无法修改ES模块具名导入的只读绑定,源码实际调用的还是原生promisify方法,不会触发你写的抛错逻辑。
  • Mock逻辑不符合执行路径:原生promisify是同步执行返回包装函数的,本身不会抛错,错误只会出现在await包装函数(即readFileProm)的异步执行阶段,直接在promisify调用时同步抛错的写法和真实错误场景不一致。
  • 断言逻辑错误:catch块捕获到的是Error实例对象,不是字符串,你原来的断言判断传入console.error的是字符串An error has occurred,就算错误被捕获也会断言失败。
正确实现方案

方案1:使用jest.mock整体mock util模块(无需修改源码,最稳定)

直接在测试文件顶部mock整个util模块,手动实现promisify逻辑,让它返回的包装函数调用时返回失败状态的Promise,错误会在await阶段被catch块捕获。

import { read } from '../src/index';

// 全局mock util模块
jest.mock('util', () => ({
  promisify: jest.fn(() => {
    // 模拟promisify正常返回包装函数,函数执行时返回rejected Promise
    return jest.fn().mockRejectedValue(new Error('An error has occurred'))
  })
}));

describe(`Read Function`, () => {
  let consoleErrorSpy;
  beforeEach(() => {
    // 监听console.error,同时mock实现避免测试时打印错误日志
    consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
    // 清空所有mock的调用记录
    jest.clearAllMocks();
  });
  afterEach(() => {
    // 恢复原生console实现,避免影响其他测试用例
    consoleErrorSpy.mockRestore();
  });

  it(`should call 'console.error' with error message 'An error has occurred'`, async () => {
    await read();
    // 断言console.error被调用,且入参是包含指定message的Error对象
    expect(consoleErrorSpy).toHaveBeenCalledWith(
      expect.objectContaining({
        message: 'An error has occurred'
      })
    );
  });
});

方案2:调整源码导入方式配合spyOn

如果你希望用spyOn的方式实现mock,可以修改源码的promisify导入方式,改为导入整个util模块再调用方法,让spyOn能正确拦截调用:

  1. 首先修改index.ts的导入和调用逻辑:
import * as util from 'util';
import { readFile } from 'fs';

export async function read(): Promise<string> {
  try {
    // 改为通过util对象调用promisify
    const readFileProm = util.promisify(readFile);
    const content = await readFileProm(__filename);
    console.log(content.toString());
    return content.toString();
  } catch (error) {
    console.error(error);
  }
}
  1. 调整测试用例的spyOn逻辑和断言:
import { read } from '../src/index';
import * as util from 'util';

describe(`Read Function`, () => {
  let consoleErrorSpy;
  beforeEach(() => {
    consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
    jest.clearAllMocks();
  });
  afterEach(() => {
    consoleErrorSpy.mockRestore();
    // 恢复所有spy的原生实现
    jest.restoreAllMocks();
  });

  it(`should call 'console.error' with error message 'An error has occurred'`, async () => {
    jest.spyOn(util, 'promisify').mockReturnValue(
      jest.fn().mockRejectedValue(new Error('An error has occurred'))
    );
    await read();
    expect(consoleErrorSpy).toHaveBeenCalledWith(
      expect.objectContaining({ message: 'An error has occurred' })
    );
  });
});

优化提示:更合理的单元测试思路是不mock promisify(Node内置工具方法无需验证正确性),直接mock fs模块的readFile方法让其触发错误,只验证自身业务逻辑的分支覆盖即可。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 11:45:35