测试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
相关产品推荐
相关产品推荐

