译零 TransZero · TanStack Query 中文文档
主站

测试

更新于 2026-11-02 React 查看英文原文 ↗

React Query 通过 Hook 实现功能——既可以使用我们提供的 Hook,也可以使用封装了这些 Hook 的自定义 Hook。

你可以使用 React Testing Library 编写这些自定义 Hook 的单元测试,它提供了 renderHook 并重新导出了 @testing-library/dom 等辅助函数,例如 waitFor。

通过运行以下命令安装:

Shell
1npm install @testing-library/react @testing-library/dom --save-dev

我们的第一项测试

安装完成后,即可编写一个简单测试。假设有如下自定义 Hook:

TypeScript
1export function useCustomHook() {2  return useQuery({ queryKey: ['customHook'], queryFn: () => 'Hello' })3}

我们可以为其编写如下测试:

TypeScript
1import { renderHook, waitFor } from '@testing-library/react'2 3const queryClient = new QueryClient()4const wrapper = ({ children }) => (5  <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>6)7 8const { result } = renderHook(() => useCustomHook(), { wrapper })9 10await waitFor(() => expect(result.current.isSuccess).toBe(true))11 12expect(result.current.data).toEqual('Hello')

注意,我们提供了一个自定义包装器,用于构建 QueryClient 和 QueryClientProvider。这有助于确保我们的测试完全与其他测试隔离。

该包装器只需编写一次即可,但此时必须确保 QueryClient 在每次测试前都被清空,并且测试不能并行执行;否则一个测试会影响其他测试的结果。

关闭重试

该库默认启用三次带指数退避的重试,这意味着如果你希望测试一个出错的查询,测试很可能会超时。最简单的禁用重试方式是通过 QueryClientProvider。我们来扩展上面的示例:

TypeScript
1const queryClient = new QueryClient({2  defaultOptions: {3    queries: {4      // ✅ turns retries off5      retry: false,6    },7  },8})9const wrapper = ({ children }) => (10  <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>11)

这会将组件树中所有查询的默认重试次数设为“零次”。需要注意的是,仅当实际的 useQuery 未显式设置 retries 选项时,此配置才生效;如果某个查询明确设置了 5 次重试,则该设置仍会优先生效,因为默认值仅作为回退方案。

使用 Jest 将 gcTime 设为 Infinity

如果你使用 Jest,可将 gcTime 设为 Infinity,以避免出现 “Jest 在测试运行完成一秒钟后仍未退出” 的错误提示。这是服务端的默认行为;仅当你显式设置了 gcTime 时,才需要手动配置此项。

测试网络请求

React Query 的主要用途是缓存网络请求,因此我们必须首先验证代码是否发出了正确的网络请求。

测试网络请求的方式有很多,本例中我们将使用 nock。

假设有如下自定义 Hook:

TypeScript
1function useFetchData() {2  return useQuery({3    queryKey: ['fetchData'],4    queryFn: () => request('/api/data'),5  })6}

我们可以为其编写如下测试:

TypeScript
1const queryClient = new QueryClient()2const wrapper = ({ children }) => (3  <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>4)5 6const expectation = nock('http://example.com').get('/api/data').reply(200, {7  answer: 42,8})9 10const { result } = renderHook(() => useFetchData(), { wrapper })11 12await waitFor(() => expect(result.current.isSuccess).toBe(true))13 14expect(result.current.data).toEqual({ answer: 42 })

此处我们利用 waitFor 并等待查询状态表明请求已成功完成。这样我们就能确认 Hook 已执行完毕,且应已获得正确的数据。 //: # 'NoteWaitFor1'

测试加载更多 / 无限滚动

首先,我们需要模拟 API 响应:

TypeScript
1function generateMockedResponse(page) {2  return {3    page: page,4    items: [...]5  }6}

然后,我们的 nock 配置需根据页码区分响应,我们将借助 uri 实现这一点。 uri 的值在此处类似 /api/data?page=1 或 /api/data?page=2

TypeScript
1const expectation = nock('http://example.com')2  .persist()3  .get('/api/data')4  .query(true)5  .reply(200, (uri) => {6    const url = new URL(`http://example.com${uri}`)7    const { page } = Object.fromEntries(url.searchParams)8    return generateMockedResponse(Number(page))9  })

(注意 .persist(),因为我们将会多次调用该端点)

现在我们可以安全地运行测试,关键在于等待数据断言通过:

TypeScript
1const { result } = renderHook(() => useInfiniteQueryCustomHook(), {2  wrapper,3})4 5await waitFor(() => expect(result.current.isSuccess).toBe(true))6 7expect(result.current.data.pages).toStrictEqual([generateMockedResponse(1)])8 9result.current.fetchNextPage()10 11await waitFor(() =>12  expect(result.current.data.pages).toStrictEqual([13    generateMockedResponse(1),14    generateMockedResponse(2),15  ]),16)17 18expectation.done()

进一步阅读

如需更多技巧及一种基于 mock-service-worker 的替代配置方案,请参阅 TkDodo 撰写的《Testing React Query》一文。

本页为 TanStack Query 文档(React 版)的非官方中文翻译,原文由上游团队维护,遵循 MIT 许可。 译文如有偏差,以英文原文为准。

译自上游 59d11de