使用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
相关产品推荐
相关产品推荐

