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

使用Jest默认Mock时,Mock函数调用未被正确记录的问题

Jest Mock函数调用记录不统计的问题分析与解决

我用jest.fn()创建的Mock函数明明已经被调用,但调用记录没被正确统计。排除了忘记await异步调用的情况——控制台能看到signIn先打印,再打印clicked,说明调用确实发生了,但断言就是报错说调用次数为0。

测试代码

import { render,screen } from '@testing-library/react';
import { default as userEvent } from '@testing-library/user-event';
import { signIn } from 'hooks/__mocks__/auth';
import { LoginButton } from './LoginButton';

jest.mock('hooks/auth');

describe('<LoginButton />', () => {
  beforeEach(()=>render(<LoginButton />));
  it('should trigger the sign-in process on click', async () => {
    const user = userEvent.setup();
    await user.click(screen.getByRole('button', { name: 'Sign in' }));
    console.log("clicked") // 第二个打印的内容
    expect(signIn).toHaveBeenCalledTimes(1); // 报错:实际调用次数为0
  });
});

组件代码

export const LoginButton: React.FC = () => {
  const signIn = useSignIn();
  return (
    <Button color="secondary" onClick={signIn}>
      Login
    </Button>
  );
};

原Mock文件(hooks/mocks/auth.js)

export const signIn = jest.fn();
export const useSignIn = () => ()=>{
  console.log('signIn'); // 第一个打印的内容
  signIn()
};

奇怪的现象

如果在测试文件里把console.log("clicked")换成signIn(),断言就能通过;而且直接在测试文件里内联Mock就能正常工作:

const signIn = jest.fn();
jest.mock('hooks/auth', ()=>({
  useSignIn:() => () =>{
    console.log('signIn');
    signIn()
  }
}));

问题原因

核心是模块导入的引用不一致:
当你用jest.mock('hooks/auth')加载独立的Mock文件时,Jest的模块Mock机制可能会对模块做隔离处理,导致测试文件里导入的signIn,和Mock文件中useSignIn闭包内调用的signIn,变成了两个不同的函数实例——虽然名字一样,但内存引用完全不同,所以测试里的断言捕获不到闭包内的调用记录。

而内联Mock时,signIn是在测试文件的作用域里定义的,useSignIn直接引用这个作用域内的函数,自然能正确统计调用次数。


解决方案

方案一:简化Mock逻辑,直接返回同一个函数

修改hooks/__mocks__/auth.js,让useSignIn直接返回导出的signIn函数,避免嵌套闭包导致的引用分离:

export const signIn = jest.fn();
export const useSignIn = () => signIn; // 直接返回Mock函数,无需嵌套

方案二:确保Mock文件内引用一致

如果必须保留嵌套调用的逻辑,在Mock文件里用中间变量统一引用:

const _signIn = jest.fn();
export const signIn = _signIn;
export const useSignIn = () => () => {
  console.log('signIn');
  _signIn(); // 引用同一个中间变量
};

方案三:在测试文件中重新指定Mock实现

在测试里覆盖useSignIn的实现,确保它引用的是你导入的signIn:

import { render,screen } from '@testing-library/react';
import { default as userEvent } from '@testing-library/user-event';
import { signIn, useSignIn } from 'hooks/__mocks__/auth';
import { LoginButton } from './LoginButton';

jest.mock('hooks/auth');

// 重新绑定useSignIn的实现,确保引用导入的signIn
useSignIn.mockImplementation(() => () => {
  console.log('signIn');
  signIn();
});

describe('<LoginButton />', () => {
  beforeEach(()=>render(<LoginButton />));
  it('should trigger the sign-in process on click', async () => {
    const user = userEvent.setup();
    await user.click(screen.getByRole('button', { name: 'Sign in' }));
    expect(signIn).toHaveBeenCalledTimes(1);
  });
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 23:32:18