Vitest中Mock React子组件时栈溢出问题及测试方法
解决Vitest测试中Mock子组件导致的栈溢出问题
我想要测试父组件中的子组件在被Mock后是否能携带正确的props被调用,但运行测试时出现错误:RangeError: Maximum call stack size exceeded
我的Vitest配置
import svgr from 'vite-plugin-svgr' import { defineConfig } from 'vitest/config' import type { UserConfig } from 'vitest/config' import { resolve } from 'path' export default defineConfig({ test: { environment: 'jsdom', setupFiles: ['./tests/setup.ts'], environmentMatchGlobs: [['./src/**/*.tsx', 'jsdom']], globals: true, }, resolve: { alias: [{ find: '@', replacement: resolve(__dirname, './src') }], }, plugins: [svgr()], } as UserConfig)
我的测试代码
import { render, screen } from '@testing-library/react' import { vi } from 'vitest' import Input from '@/components/Input/Input' import Login from './Login' vi.mock('@/components/Input/Input', () => ({ default: vi.fn(), })) describe('ConfirmEmail', () => { it('renders correctly', () => { render(<Login />) expect(screen.getByRole('heading', { name: /Login to your account/i })) expect(Input).toHaveBeenCalledWith({ label: 'email', }) // error: RangeError: Maximum call stack size exceeded }) })
问题原因
你把Input组件Mock成了一个空的Vitest函数,但React组件在渲染时必须返回有效的React元素,空函数返回undefined会导致React渲染流程异常,进而触发无限递归调用,最终引发栈溢出。
解决方案
1. 修复Mock组件的返回值
确保Mock的Input组件返回一个有效的React元素,同时保留对调用参数的追踪:
vi.mock('@/components/Input/Input', () => ({ default: vi.fn(() => <div data-testid="mock-input" />), }))
2. 修正断言逻辑
React会自动给组件传递额外参数(如key、ref或上下文),直接用toHaveBeenCalledWith全量匹配props会失败,改用expect.objectContaining做部分匹配:
expect(Input).toHaveBeenCalledWith( expect.objectContaining({ label: 'email', }), expect.anything() // 匹配React自动传递的第二个参数 )
3. 补全断言方法
第一个expect缺少断言方法,需要添加.toBeInTheDocument()完成断言。
修改后的完整测试代码
import { render, screen } from '@testing-library/react' import { vi } from 'vitest' import Input from '@/components/Input/Input' import Login from './Login' vi.mock('@/components/Input/Input', () => ({ default: vi.fn(() => <div data-testid="mock-input" />), })) describe('Login', () => { // 修正描述名称,与组件对应 it('renders correctly and passes correct props to Input', () => { render(<Login />) // 验证标题存在 expect(screen.getByRole('heading', { name: /Login to your account/i })).toBeInTheDocument() // 验证Input组件被调用时携带正确的label props expect(Input).toHaveBeenCalledWith( expect.objectContaining({ label: 'email', }), expect.anything() ) }) })
内容的提问来源于stack exchange,提问作者TechDan
相关产品推荐
相关产品推荐

