
在 React 应用开发中,自定义 Hook 是封装可复用逻辑的强大工具,尤其当它们涉及到数据获取时,react-query (或 TanStack Query) 常常是首选。然而,当一个自定义 Hook 内部包含多个 useQuery 调用以获取不同数据时,如何对其进行有效且可靠的测试,常常会遇到挑战。本教程将深入探讨测试此类 Hook 时常见的陷阱,并提供一套健壮的解决方案。
考虑一个自定义 Hook,它通过 react-query 同时获取用户数据和用户状态:
// TestHook.js
import { useQuery } from "react-query";
import { getTestByUid, getTestStatusesByUid } from "./api"; // 假设 API 在单独的文件中
export const useTest = (uid) => {
const { data: test } = useQuery(["test", uid], () => getTestByUid(uid));
const { data: testStatuses } = useQuery(["statuses", uid], () => getTestStatusesByUid(uid));
return {
test,
testStatuses,
};
};在测试上述 Hook 时,开发者可能遇到以下问题:
为了克服上述挑战,我们将采用以下策略:
使用 jest.mock() 对整个 API 模块进行模拟,然后在每个测试用例中,利用模拟函数的 mockResolvedValue() 或 mockRejectedValue() 方法,为特定的 API 调用设置预期的返回值。这确保了每个测试用例都拥有一个干净且独立的模拟环境。
// api.js
// 这是一个模拟的 API 模块,实际应用中会包含真实的 API 调用逻辑
export const getTestByUid = (uid) => {
// 实际的 API 调用
return Promise.resolve({ id: uid, name: "real test data" });
};
export const getTestStatusesByUid = (uid) => {
// 实际的 API 调用
return Promise.resolve(["real_status_1", "real_status_2"]);
};在测试文件中,我们首先模拟整个 api.js 模块:
// test-hook.test.js
import * as testApi from './api'; // 引入 API 模块
jest.mock('./api'); // 在文件顶部模拟整个 API 模块在每个 it 或 test 块内部,为所有相关的 API 调用设置其 mockResolvedValue。这样,即使一个 Hook 内部有多个异步操作,每个操作的模拟值都是明确且独立的,不会受到其他测试用例的影响。
如果一个自定义 Hook 的多个输出是其核心功能的一部分,并且它们在逻辑上是紧密关联的,那么将它们的断言合并到一个测试用例中会更高效和清晰。这减少了重复的 renderHook 调用和 waitForNextUpdate 等待。
useQuery Hook 的 data 属性直接返回 API Promise 解析后的值。因此,当模拟 API 函数时,mockResolvedValue 应该直接返回期望的数据,而不是一个包含 data 属性的对象。
错误示例: testApi.getTestByUid.mockResolvedValue({ data: { name: 'secret test' } });正确示例: testApi.getTestByUid.mockResolvedValue({ name: 'secret test' });
以下是根据上述最佳实践重构后的测试代码:
// src/api/test-api.js
// 实际应用中的 API 调用函数
export const getTestByUid = (uid) => {
// 假设这里是实际的 axios.get(...) 或 fetch(...) 调用
return Promise.resolve({ id: uid, name: "default test" });
};
export const getTestStatusesByUid = (uid) => {
// 假设这里是实际的 axios.get(...) 或 fetch(...) 调用
return Promise.resolve(["default_status_1", "default_status_2"]);
};// src/hooks/TestHook.js
import { useQuery } from "react-query";
import { getTestByUid, getTestStatusesByUid } from "../api/test-api";
export const useTest = (uid) => {
const { data: test } = useQuery(["test", uid], () => getTestByUid(uid));
const { data: testStatuses } = useQuery(["statuses", uid], () => getTestStatusesByUid(uid));
return {
test,
testStatuses,
};
};// test/test-hook.test.js
import { renderHook } from "@testing-library/react-hooks";
import { QueryClient, QueryClientProvider } from "react-query";
import { useTest } from "../src/hooks/TestHook";
import * as testApi from "../src/api/test-api"; // 引入 API 模块
import React from "react";
// 在文件顶部模拟整个 API 模块
jest.mock("../src/api/test-api");
// 创建一个 QueryClient 实例,并配置默认选项,例如禁用重试
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false, // 在测试中禁用重试,避免不必要的等待
},
},
});
// 创建一个包装器组件,用于提供 QueryClientProvider
const wrapper = ({ children }) => {
return (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
};
describe("useTestHook", () => {
it("应该正确返回测试数据和状态", async () => {
// 为当前测试用例模拟所有相关的 API 调用
testApi.getTestByUid.mockResolvedValue({ name: "secret test" });
testApi.getTestStatusesByUid.mockResolvedValue([
"in_progress",
"ready_for_approval",
"rejected",
]);
// 渲染 Hook
const { result, waitForNextUpdate } = renderHook(
() => useTest("bb450409-d778-4d57-a4b8-70fcfe2087bd"),
{ wrapper }
);
// 等待 Hook 内部的异步操作完成并更新
await waitForNextUpdate();
// 断言 Hook 返回的测试数据
expect(result.current.test).toEqual({ name: "secret test" });
// 断言 Hook 返回的测试状态
expect(result.current.testStatuses).toEqual([
"in_progress",
"ready_for_approval",
"rejected",
]);
});
// 可以添加其他测试用例,例如测试错误状态、加载状态等
it("应该在 API 调用失败时处理错误", async () => {
const errorMessage = "Failed to fetch data";
testApi.getTestByUid.mockRejectedValue(new Error(errorMessage));
testApi.getTestStatusesByUid.mockResolvedValue([]); // 即使一个失败,另一个也可能成功或被模拟
const { result, waitForNextUpdate } = renderHook(
() => useTest("some-uid"),
{ wrapper }
);
await waitForNextUpdate();
// 假设 useQuery 的错误会被 Hook 内部处理或暴露
// 这里我们只关注 getTestByUid 的错误,testStatuses 可能是默认值或空
// 实际断言取决于 Hook 如何处理错误
// expect(result.current.testError).toBeInstanceOf(Error);
// expect(result.current.testError.message).toBe(errorMessage);
expect(result.current.test).toBeUndefined(); // 如果 Hook 没有特殊处理,失败的查询数据将是 undefined
expect(result.current.testStatuses).toEqual([]);
});
});通过遵循这些原则,你可以有效地测试包含多个 useQuery 的 React 自定义 Hook,确保其功能的健壮性和可靠性。
在撰写本教程时,以下是使用的关键库版本:
以上就是如何测试包含多个 useQuery 的 React 自定义 Hook的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号