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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 11:27:31