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

如何在测试中仅Mock自定义useQuery钩子的data,忽略isLoading等字段?

环境

  • Next.js v14 Pages Router
  • jest v29.7
  • @tanstack/react-query v5.36.0

问题场景

我封装了基于@tanstack/react-query的useUserInfo自定义钩子用于获取用户信息,现在需要测试依赖它的useAccountMenu钩子——这个钩子会根据useUserInfo返回的data生成不同的菜单项。

但测试时直接mockuseUserInfo返回{ data: mockData }会触发类型错误,提示缺少isError、isPending等React Query返回的完整字段。不想每次手动硬编码这些冗余字段,求更优雅的测试方案。


方案1:用React Query官方测试工具生成Mock结果

React Query官方提供了@tanstack/react-query/testing包,其中的mockSuccessQueryResult可以直接生成符合类型要求的成功状态结果,无需手动补全所有字段。

先安装依赖(如果未安装):

npm install --save-dev @tanstack/react-query/testing

修改后的测试代码:

// useAccountMenu.test.ts
import { renderHook, waitFor } from '@testing-library/react';
import { mockSuccessQueryResult } from '@tanstack/react-query/testing';
import { useAccountMenu } from './useAccountMenu';
import { useUserInfo } from './useUserInfo';

jest.mock('./useUserInfo');

describe.concurrent('useAccountMenu', () => {
  it('返回完整菜单项当用户id ≤ 100时', async () => {
    const mockData = { id: 3, name: 'John', age: 25 };
    // 用官方工具生成符合类型的成功结果
    jest.mocked(useUserInfo).mockReturnValueOnce(mockSuccessQueryResult(mockData));

    const { result } = renderHook(() => useAccountMenu(), { wrapper: YourQueryProvider });

    await waitFor(() => {
      expect(result.current.menuItems).toEqual([
        { menuLabel: 'User Setting', to: 'setting/user' },
        { menuLabel: 'Language', to: 'setting/language' },
        { menuLabel: 'Organization', to: 'setting/organization' },
      ]);
    });
  });

  it('返回精简菜单项当用户id > 100时', async () => {
    const mockData = { id: 101, name: 'Jane', age: 30 };
    jest.mocked(useUserInfo).mockReturnValueOnce(mockSuccessQueryResult(mockData));

    const { result } = renderHook(() => useAccountMenu(), { wrapper: YourQueryProvider });

    await waitFor(() => {
      expect(result.current.menuItems).toEqual([
        { menuLabel: 'User Setting', to: 'setting/user' },
        { menuLabel: 'Language', to: 'setting/language' },
      ]);
    });
  });
});

方案2:自定义Mock辅助函数

如果不想额外安装依赖,可以自己写一个辅助函数,预设常用状态字段,按需生成Mock结果:

// test-utils.ts
import type { QueryObserverSuccessResult } from '@tanstack/react-query';
import type { ActiveUserInfo } from './type';

export const createMockUserInfoSuccess = (data: ActiveUserInfo): QueryObserverSuccessResult<ActiveUserInfo, Error> => ({
  data,
  error: null,
  isError: false,
  isPending: false,
  isLoading: false,
  isSuccess: true,
  isFetching: false,
  isRefetching: false,
  status: 'success',
  failureCount: 0,
  refetch: jest.fn(),
  // 剩余必填字段可按类型要求用undefined或空函数填充
});

测试时直接调用该函数:

// useAccountMenu.test.ts
import { createMockUserInfoSuccess } from './test-utils';

// ...
jest.mocked(useUserInfo).mockReturnValueOnce(createMockUserInfoSuccess(mockData));
// ...

方案3:重构钩子,隔离依赖简化测试

把useAccountMenu的核心逻辑提取为纯函数,让它直接接收用户信息作为参数,这样测试时无需mock钩子,直接传入测试数据即可:

// useAccountMenu.ts
// 提取核心逻辑为纯函数
export const getAccountMenuItems = (userInfo: ActiveUserInfo) => {
  if (userInfo.id > 100) {
    return [
      { menuLabel: 'User Setting', to: 'setting/user' },
      { menuLabel: 'Language', to: 'setting/language' },
    ];
  }
  return [
    { menuLabel: 'User Setting', to: 'setting/user' },
    { menuLabel: 'Language', to: 'setting/language' },
    { menuLabel: 'Organization', to: 'setting/organization' },
  ];
};

// 原钩子保持原有调用逻辑
export const useAccountMenu = () => {
  const { data } = useUserInfo();
  return { menuItems: getAccountMenuItems(data!) }; // 实际项目中需处理data为null/undefined的情况
};

测试纯函数的代码更简洁:

// useAccountMenu.test.ts
import { getAccountMenuItems } from './useAccountMenu';

describe('getAccountMenuItems', () => {
  it('用户id ≤ 100时返回完整菜单', () => {
    const mockData = { id: 3, name: 'John', age: 25 };
    expect(getAccountMenuItems(mockData)).toEqual([
      { menuLabel: 'User Setting', to: 'setting/user' },
      { menuLabel: 'Language', to: 'setting/language' },
      { menuLabel: 'Organization', to: 'setting/organization' },
    ]);
  });

  it('用户id > 100时返回精简菜单', () => {
    const mockData = { id: 101, name: 'Jane', age: 30 };
    expect(getAccountMenuItems(mockData)).toEqual([
      { menuLabel: 'User Setting', to: 'setting/user' },
      { menuLabel: 'Language', to: 'setting/language' },
    ]);
  });
});

// 如需测试钩子的集成逻辑,再补充钩子测试(此时mock useUserInfo仅需保证data正确,其他字段用辅助函数生成即可)

内容的提问来源于stack exchange,提问作者pop

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 05:35:09