React 18 + Jest 29下自定义Hook手动Mock失效求助
自定义Hook Mock失败的问题排查与修复
问题背景
使用React 18和Jest 29测试SCMovieDetail组件时,尝试通过__mocks__目录复用useFetch自定义Hook的Mock实现,但测试始终调用真实Hook,无法触发Mock逻辑。
错误原因分析
jest.mock调用时机错误:测试用例内部调用jest.mock时,组件已完成导入,Mock无法覆盖真实模块__mocks__文件命名不匹配:src/hooks/__mocks__下的文件为useFetch.tsx,但原Hook文件是useFetch.ts,Jest无法正确识别匹配的Mock文件useParams未Mock:测试中useParams返回undefined,导致useFetch的URL为http://localhost:3000/movies/undefined,无法命中Mock预设的分支- 断言逻辑不合理:测试中断言
'Hello!'存在,但组件渲染的SCServerError显示的是useFetch返回的error值,两者不匹配
修复步骤
1. 调整jest.mock调用位置
将jest.mock移到测试文件顶部,确保在组件导入前生效:
import renderer from 'react-test-renderer'; import { render, screen } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; // 先Mock依赖,再导入组件 jest.mock('../../hooks/useFetch'); jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useParams: () => ({ slug: 'error' }) })); import { SCMovieDetail } from './MovieDetail';
2. 修正__mocks__文件命名
将src/hooks/__mocks__/useFetch.tsx重命名为useFetch.ts,与原Hook文件扩展名保持一致,确保Jest能正确识别Mock模块。
3. 适配Mock的URL判断逻辑
修改useFetch的Mock实现,让它能根据实际请求URL的特征返回对应状态(比如URL包含error/loading关键词):
// src/hooks/__mocks__/useFetch.ts import { useFetchPayload, useFetchProps } from '../useFetch'; export const useFetch = <T>({ url, method = 'GET' }: useFetchProps): useFetchPayload<T> => { let data: T | undefined = undefined; let error: string | undefined = undefined; let loading = false; if (url.includes('error')) { error = `${method} request failed`; } else if (url.includes('loading')) { loading = true; } else if (url.includes('success')) { data = {} as T; } return { data, error, loading }; };
4. 修正断言逻辑
匹配Mock返回的错误信息,调整断言内容:
it('Should render error', () => { render(componentJSX()); expect(screen.getByText('GET request failed')).toBeInTheDocument(); });
完整测试代码示例
// src/components/movies/MovieDetail.test.tsx import renderer from 'react-test-renderer'; import { render, screen } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; // 提前Mock依赖 jest.mock('../../hooks/useFetch'); jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useParams: () => ({ slug: 'error' }) })); import { SCMovieDetail } from './MovieDetail'; describe('SCMovieDetail', () => { const componentJSX = () => { return ( <MemoryRouter> <SCMovieDetail /> </MemoryRouter> ); }; it('Should match snapshot', () => { const component = renderer.create(componentJSX()); const tree = component.toJSON(); expect(tree).toMatchSnapshot(); }); it('Should render error', () => { render(componentJSX()); expect(screen.getByText('GET request failed')).toBeInTheDocument(); }); });
修正后的目录结构
src/ |-- ... |-- components/ | | ... | |-- movies/ | | |-- MovieDetail.tsx | | |-- MovieDetail.test.tsx | | |-- ... |-- hooks/ | |-- __mocks__/ | | |-- useFetch.ts # 修正扩展名 | |-- useFetch.ts | |-- ... |-- ...
内容的提问来源于stack exchange,提问作者Tjeu Foolen
相关产品推荐
相关产品推荐

