React自定义Hook双GET查询测试:第二个useQuery返回undefined排查
问题
在自定义Hook useTest 中通过React Query执行两个GET请求(getTestByUid 和 getTestStatusesByUid),使用@testing-library/react-hooks和Jest测试时,第一个验证test数据的测试通过,但第二个验证testStatuses的测试中,该值返回undefined,尽管已对getTestStatusesByUid做了spy模拟。
相关代码
TestHook.js
import { useQuery } from "react-query" export const useTest = (uid) => { const { data: test } = useQuery("test", () => getTestByUid(uid)); const { data: testStatuses } = useQuery("statuses", () => getTestStatusesByUid(uid)); return { test, testStatuses } }
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.js"; const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, }, }, }); const wrapper = ({ children }) => { return ( <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> ); }; describe("useTestHook", () => { it("should return a test", async () => { jest .spyOn(testApi, "getTestByUid") .mockImplementationOnce(() => Promise.resolve({ data: { name: "secret test" } }) ); const { result, waitForNextUpdate } = renderHook( () => useTest("bb450409-d778-4d57-a4b8-70fcfe2087bd"), { wrapper: wrapper } ); await waitForNextUpdate(); expect(result.current.test).toBe({ name: "secret test" }); }); it("should return statuses for a test", async () => { jest.spyOn(testApi, "getTestStatusesByUid").mockImplementationOnce(() => Promise.resolve({ data: ["in_progress", "ready_for_approval", "rejected"], }) ); const { result, waitForNextUpdate } = renderHook( () => useTest("bb450409-d778-4d57-a4b8-70fcfe2087bd"), { wrapper: wrapper } ); await waitForNextUpdate(); expect(result.current.testStatuses).toBe([ "in_progress", "ready_for_approval", "rejected", ]); }); });
原因及解决方案
核心原因
- 未模拟全部API请求:第二个测试只模拟了
getTestStatusesByUid,但useTest会同时触发getTestByUid请求,未被模拟的请求会处于pending状态,导致组件无法更新到testStatuses返回的状态。 waitForNextUpdate的局限性:该方法仅等待一次状态更新,当Hook中有两个并行请求时,第一次更新可能只是其中一个请求完成,此时testStatuses仍为undefined。- 断言方式错误:用
toBe比较对象/数组时,是严格引用比较,而返回的新对象/数组和测试字面量不是同一引用,即使数据正确也会断言失败(这也是潜在问题)。
解决步骤
1. 每个测试模拟所有用到的API
确保Hook中调用的所有API都被模拟,避免请求挂起:
it("should return statuses for a test", async () => { // 同时模拟两个API方法 jest.spyOn(testApi, "getTestByUid").mockImplementationOnce(() => Promise.resolve({ data: { name: "secret test" } }) ); jest.spyOn(testApi, "getTestStatusesByUid").mockImplementationOnce(() => Promise.resolve({ data: ["in_progress", "ready_for_approval", "rejected"], }) ); const { result } = renderHook( () => useTest("bb450409-d778-4d57-a4b8-70fcfe2087bd"), { wrapper: wrapper } ); });
2. 用waitFor替代waitForNextUpdate
waitFor会持续等待直到断言通过,适合多并行请求场景:
await waitFor(() => { expect(result.current.test).not.toBeUndefined(); expect(result.current.testStatuses).not.toBeUndefined(); });
3. 用toEqual替代toBe比较对象/数组
toEqual递归比较值,而非引用,适合对象和数组的断言:
// 替换原有的toBe断言 expect(result.current.test).toEqual({ name: "secret test" }); expect(result.current.testStatuses).toEqual([ "in_progress", "ready_for_approval", "rejected", ]);
4. 测试后清理模拟和QueryClient
避免测试间污染,在afterEach中添加清理逻辑:
afterEach(() => { jest.clearAllMocks(); queryClient.clear(); });
内容的提问来源于stack exchange,提问作者Tony96
相关产品推荐
相关产品推荐

