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

测试React自定义Hook时Jest Mock返回undefined问题求助

React Query自定义Hook测试中Mock返回undefined的解决方法

我用Create React App创建了一个应用,自定义Hook通过React Query调用CustomerService获取客户数据。尝试用React Testing Library和Jest给这个Hook写单元测试,但Jest.fn()创建的Mock一直返回undefined。已经试过在package.json里设置"resetMocks": false,也用了Spy等不同Mock方式,都没解决。相关代码如下:

自定义Hook

import { useQuery } from "react-query";
import CustomerService from "./CustomerService";

const useCustomers = (customerId: number) => {
  const response = useQuery(["customers", customerId], () => {
    return CustomerService.getCustomer(customerId);
  });
  const { data, isSuccess } = response;

  return { data, isSuccess };
};

export default useCustomers;

服务代码

const getCustomer = async (customerId: number) => {
  try {
    const response = await fetch(`/customers/${customerId}`);
    const data = await response.json();

    return data;
  } catch (error) {
    console.log(error);
    throw error;
  }
};

const CustomerService = {
  getCustomer,
};

export default CustomerService;

单元测试代码

import { render, screen } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "react-query";
import useCustomers from "./useCustomers";

const queryClient = new QueryClient();

const TestingComponent = () => {
  const { data } = useCustomers(1);
  return (
    <>
      <h1>Customer ID: {data.ID}</h1>
    </>
  );
};

const mockGetCustomer = jest
  .fn()
  .mockResolvedValue(() => JSON.parse('{"ID":"123"}'));
jest.doMock("./CustomerService", () => {
  return {
    getCustomer: mockGetCustomer,
  };
});

describe("useCustomers hook", () => {
  test("provides expected response from CustomerService", () => {
    mockGetCustomer.mockResolvedValue(() => JSON.parse('{"ID":"123"}'));

    render(
      <QueryClientProvider client={queryClient}>
        <TestingComponent />
      </QueryClientProvider>
    );

    const name = screen.getByText("123");
    expect(name).toBeInTheDocument();
  });
});

package.json配置

{
  "name": "jest-mocks",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/user-event": "^13.5.0",
    "@types/jest": "^27.5.2",
    "@types/node": "^16.18.24",
    "@types/react": "^18.0.38",
    "@types/react-dom": "^18.0.11",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-query": "^3.39.3",
    "react-scripts": "5.0.1",
    "typescript": "^4.9.5",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "@testing-library/react": "^14.0.0",
    "jest": "^27.5.1",
    "jest-junit": "^16.0.0"
  },
  "jest": {
    "resetMocks": false
  }
}

问题原因及修复方案

1. mockResolvedValue 使用错误

你当前给mockResolvedValue传了一个返回解析对象的函数,但mockResolvedValue需要的是直接resolve的最终值,而不是返回值的函数。这导致Mock实际返回的是这个函数,而非预期的客户对象,最终data为undefined。

修复方式:直接传入目标对象或执行解析后的结果:

// 写法1:直接传递对象
mockGetCustomer.mockResolvedValue({ ID: "123" });

// 写法2:直接执行JSON.parse
mockGetCustomer.mockResolvedValue(JSON.parse('{"ID":"123"}'));

2. Jest Mock时机问题

在Create React App环境中,jest.doMock的位置容易导致Mock不生效,建议改用jest.mock(会自动提升到模块顶部执行),确保被测试模块导入前Mock已生效:

// 先Mock模块,再导入被测试代码
jest.mock("./CustomerService", () => ({
  getCustomer: jest.fn().mockResolvedValue({ ID: "123" })
}));

import { render, screen } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "react-query";
import useCustomers from "./useCustomers";
import CustomerService from "./CustomerService";

// 类型断言,方便后续操作Mock
const mockGetCustomer = CustomerService.getCustomer as jest.Mock;

3. 处理异步渲染与空值

React Query的请求是异步的,不能用同步的screen.getByText,需改用异步的await screen.findByText等待数据加载完成。同时要处理data为undefined的情况,避免渲染报错:

const TestingComponent = () => {
  const { data } = useCustomers(1);
  return (
    <>
      <h1>Customer ID: {data?.ID || "Loading..."}</h1>
    </>
  );
};

// 测试用例中改为异步
test("provides expected response from CustomerService", async () => {
  mockGetCustomer.mockResolvedValue({ ID: "123" });

  render(
    <QueryClientProvider client={queryClient}>
      <TestingComponent />
    </QueryClientProvider>
  );

  const customerIdElement = await screen.findByText("123");
  expect(customerIdElement).toBeInTheDocument();
  // 验证Mock被正确调用
  expect(mockGetCustomer).toHaveBeenCalledWith(1);
});

4. 可选:清理测试环境

为避免测试间的状态污染,可在每个测试前重置Query Client和Mock:

beforeEach(() => {
  queryClient.clear();
  mockGetCustomer.mockClear();
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 00:00:41