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

同文件内函数互调时,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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 10:43:18