同文件内函数互调时,Jest无法侦测函数调用的问题
同文件内函数互相调用的单元测试问题解决
问题场景
你有以下TypeScript函数,isParentFeatureIdValid和validateParentFeatureId在同一文件中:
export function validateParentFeatureId(parentFeatureId: string | null) { const isIdvalid = isParentFeatureIdValid(parentFeatureId); if (!isIdvalid) throw new BadRequestError(FeatureErrorMessages.FeatureParentIdInvalid); } // 同文件内的另一个函数 export function isParentFeatureIdValid(id: string | null) { // 验证逻辑 }
编写单元测试时尝试mockisParentFeatureIdValid,但测试失败:
import * as validators from './validators'; // 假设mockedIsParentFeatureIdInValid是预先定义的mock it('Should invoke isParentFeatureIdValid when validateParentFeatureId is invoked', () => { mockedIsParentFeatureIdInValid.mockReturnValueOnce(true); validators.validateParentFeatureId(null); expect(mockedIsParentFeatureIdInValid).toBeCalled(); });
得到错误:
expect(jest.fn()).toBeCalled() Expected number of calls: >= 1 Received number of calls: 0
原因分析
核心问题是:同文件内的函数直接调用时,Jest的Mock/Spy无法拦截内部引用。validateParentFeatureId中调用的是本地的isParentFeatureIdValid函数引用,而你mock的是模块导出对象上的函数副本。两者指向不同的引用,所以内部调用不会触发mock的函数。
解决方案
方案1:修改内部调用为模块自身引用
通过导入模块自身,让内部调用指向导出对象上的方法,这样Spy就能生效:
// 假设当前文件名为validators.ts import * as self from './validators'; export function validateParentFeatureId(parentFeatureId: string | null) { // 改为通过self调用导出的函数 const isIdvalid = self.isParentFeatureIdValid(parentFeatureId); if (!isIdvalid) throw new BadRequestError(FeatureErrorMessages.FeatureParentIdInvalid); } export function isParentFeatureIdValid(id: string | null) { // 原验证逻辑 }
测试代码调整为:
import * as validators from './validators'; it('Should invoke isParentFeatureIdValid when validateParentFeatureId is invoked', () => { const spy = jest.spyOn(validators, 'isParentFeatureIdValid').mockReturnValueOnce(true); validators.validateParentFeatureId(null); expect(spy).toBeCalled(); spy.mockRestore(); // 测试后清理mock });
方案2:手动Mock整个模块
如果不想修改源码,可以通过jest.mock重写模块导出,让内部调用指向mock函数:
import * as validators from './validators'; // 保留模块原有导出,仅mock目标函数 jest.mock('./validators', () => { const originalModule = jest.requireActual('./validators'); return { ...originalModule, isParentFeatureIdValid: jest.fn(), }; }); it('Should invoke isParentFeatureIdValid when validateParentFeatureId is invoked', () => { const mockedValidator = validators.isParentFeatureIdValid as jest.Mock; mockedValidator.mockReturnValueOnce(true); validators.validateParentFeatureId(null); expect(mockedValidator).toBeCalledWith(null); });
方案3:将验证函数作为参数传入(侵入性最小)
修改validateParentFeatureId,允许传入自定义验证函数,测试时传入mock:
// 源码调整 export function validateParentFeatureId( parentFeatureId: string | null, // 默认使用原函数 validator = isParentFeatureIdValid ) { const isIdvalid = validator(parentFeatureId); if (!isIdvalid) throw new BadRequestError(FeatureErrorMessages.FeatureParentIdInvalid); } export function isParentFeatureIdValid(id: string | null) { // 原验证逻辑 }
测试代码:
import { validateParentFeatureId } from './validators'; it('Should invoke the validator function when validateParentFeatureId is invoked', () => { const mockValidator = jest.fn().mockReturnValueOnce(true); validateParentFeatureId(null, mockValidator); expect(mockValidator).toBeCalledWith(null); });
内容的提问来源于stack exchange,提问作者Karim Fayed
相关产品推荐
相关产品推荐

