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能正确拦截调用:
- 首先修改
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); } }
- 调整测试用例的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内置工具方法无需验证正确性),直接mockfs模块的readFile方法让其触发错误,只验证自身业务逻辑的分支覆盖即可。
内容的提问来源于stack exchange,提问作者meddy
相关产品推荐
相关产品推荐

