Jest中Mock自定义useFetch Hook失败,调用原API且报错如何解决?
问题
尽管已Mock自定义useFetch Hook,但渲染使用该Hook的App组件时,仍会调用原API获取数据,且测试出现如下错误:
Matcher error: received value must be a mock or spy function
Received has value: undefined
测试代码如下:
test('renders table when data is loaded', async () => { jest.mock('./hooks/useFetch', () => ({ __esModule: true, default: () => { const original = jest.requireActual('./hooks/useFetch'); return { ...original, fetchData: jest.fn() } } })); const mockFetchData = jest.fn().mockImplementation((data) => { global.fetch = jest.fn().mockResolvedValueOnce(data); }); const mockData = [ { id: 1, title: 'Type 1' }, { id: 2, title: 'Type 2' }, ]; render(<App />); await waitFor(() => expect(mockFetchData(mockData)).toHaveBeenCalledTimes(1)) });
自定义Hook代码如下:
const useFetch = () => { const fetchData = useCallback( async (url, callback) => { try { const response = await fetch(url) const data = await response.json(); callback(data) } catch (error) { setError(error.message || 'An error occurred') } },[]); return { fetchData } }; export default useFetch;
注:该Hook中的callback是App组件中用于设置数据状态的setData函数,曾尝试相关方案但未成功。
原因及修复方案
1. Jest.mock的位置错误
jest.mock必须放在测试文件的顶层作用域,不能嵌套在test函数内部。Jest会在预处理阶段扫描并执行所有顶层的jest.mock,如果放在测试函数里,App组件已经提前导入了原Hook,Mock不会生效。
2. Mock的Hook与测试中的mock函数无关联
当前Mock里创建了一个独立的jest.fn(),但和测试里定义的mockFetchData没有任何绑定关系。App组件实际使用的是Mock内的空函数,而你断言的是另一个从未被调用的函数,因此出现undefined错误。
3. Mock实现不符合原Hook的参数逻辑
原Hook的fetchData接收url和callback两个参数,但你的mockFetchData只处理了data参数,且替换global.fetch的逻辑完全偏离了原Hook的调用流程。
修复后的测试代码
// 将jest.mock移至顶层作用域 jest.mock('./hooks/useFetch', () => ({ __esModule: true, default: jest.fn() })); import useFetch from './hooks/useFetch'; import { render, waitFor } from '@testing-library/react'; import App from './App'; test('renders table when data is loaded', async () => { const mockData = [ { id: 1, title: 'Type 1' }, { id: 2, title: 'Type 2' }, ]; // 定义符合原Hook参数结构的mock函数 const mockFetchData = jest.fn((url, callback) => { // 直接调用callback返回模拟数据,跳过真实API请求 callback(mockData); }); // 让Mock的useFetch返回我们控制的fetchData useFetch.mockReturnValue({ fetchData: mockFetchData }); render(<App />); // 直接断言mock函数的调用次数,不要主动调用它 await waitFor(() => expect(mockFetchData).toHaveBeenCalledTimes(1)); });
额外说明
- 无需使用
jest.requireActual,因为我们完全需要Mock这个Hook的返回值,不需要保留原实现的任何部分。 - Mock后的
fetchData会直接执行传入的callback,不会触发原Hook里的真实fetch请求。 - 断言时不要调用
mockFetchData(mockData),否则会额外执行一次函数,干扰断言结果。
内容的提问来源于stack exchange,提问作者bakrall
相关产品推荐
相关产品推荐

