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

测试

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

Vue Query 通过组合式函数(composables)实现功能——这些组合式函数既可以是官方提供的,也可以是你自己封装的、内部调用了官方组合式函数的自定义组合式函数。

这些组合式函数需要 QueryClient,而 VueQueryPlugin 正是提供该对象的工具,因此它们必须在已安装插件的组件的 setup 中运行。你可以使用 Vue Test Utils 来为它们编写单元测试。

环境搭建

遵循 Vue 官方文档中 Testing Composables 的方式,我们编写一个 withSetup 辅助函数:该函数挂载一个组件,在其 setup 中运行组合式函数,并安装带有 QueryClient 的 VueQueryPlugin。该辅助函数在 Vue 3 和 Vue 2 中有所不同,因为二者使用的 Vue Test Utils 主版本不同。

Vue 3

通过运行以下命令安装 Vue Test Utils 2:

Shell
1npm install @vue/test-utils --save-dev
TypeScript
1import { defineComponent } from 'vue'2import { flushPromises, mount } from '@vue/test-utils'3import { QueryClient, VueQueryPlugin } from '@tanstack/vue-query'4 5function withSetup<T>(composable: () => T, queryClient = new QueryClient()) {6  let result!: T7  const wrapper = mount(8    defineComponent({9      setup() {10        result = composable()11        return () => null12      },13    }),14    { global: { plugins: [[VueQueryPlugin, { queryClient }]] } },15  )16  return [result, wrapper] as const17}

Vue 2

Vue Test Utils 1 未导出 flushPromises,因此需同时安装 flush-promises 包:

Shell
1npm install @vue/test-utils@1 flush-promises --save-dev
TypeScript
1import { defineComponent } from 'vue'2import { createLocalVue, mount } from '@vue/test-utils'3import flushPromises from 'flush-promises'4import { QueryClient, VueQueryPlugin } from '@tanstack/vue-query'5 6function withSetup<T>(composable: () => T, queryClient = new QueryClient()) {7  let result!: T8  const localVue = createLocalVue()9  localVue.use(VueQueryPlugin, { queryClient })10  const wrapper = mount(11    defineComponent({12      setup() {13        result = composable()14        return () => null15      },16    }),17    { localVue },18  )19  return [result, wrapper] as const20}

在 Vue 2.6 中,还需在安装 VueQueryPlugin 前调用 localVue.use(VueCompositionApi),并从 @vue/composition-api 中导入 VueCompositionApi 和 defineComponent。

我们的第一个测试

完成环境搭建后,即可编写一个简单测试。假设有如下自定义组合式函数:

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

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

TypeScript
1const [result, wrapper] = withSetup(() => useCustomComposable())2 3await flushPromises()4 5expect(result.isSuccess.value).toBe(true)6expect(result.data.value).toEqual('Hello')7 8wrapper.unmount()

在 Vue Test Utils 1 中,请调用 wrapper.destroy() 而非 wrapper.unmount()。

注意 withSetup 每次调用都会创建一个新的 QueryClient,这有助于确保测试完全与其他测试隔离。在测试末尾卸载包装器(wrapper),可移除组合式函数所创建的查询观察者(query observers)。

你也可以在多个测试间共享单个 QueryClient,但此时必须确保每次测试前都清空该 QueryClient,且测试不可并行执行;否则一个测试会影响其他测试的结果。

此处仅需 flushPromises 即可,因为查询函数会立即解析。当查询函数依赖定时器时,请使用模拟计时器(fake timers)并推进时间,例如在 Vitest 中使用 vi.advanceTimersByTimeAsync。在 afterEach 中恢复真实计时器,以确保即使断言失败,计时器也能被正确还原:

TypeScript
1beforeEach(() => {2  vi.useFakeTimers()3})4 5afterEach(() => {6  vi.useRealTimers()7})8 9it('should resolve the delayed query', async () => {10  const [result, wrapper] = withSetup(() =>11    useQuery({12      queryKey: ['delayed'],13      queryFn: () =>14        new Promise((resolve) => setTimeout(() => resolve('Hello'), 1000)),15    }),16  )17 18  await vi.advanceTimersByTimeAsync(1000)19 20  expect(result.data.value).toEqual('Hello')21 22  wrapper.unmount()23})

当查询函数等待真实请求时,则应等待断言通过,例如使用 vi.waitFor:

TypeScript
1await vi.waitFor(() => expect(result.isSuccess.value).toBe(true))

对于更复杂的组合式函数,直接测试使用它的组件可能更简便:以相同的插件配置挂载该组件,并对其渲染结果进行断言。

关闭重试机制

该库默认对失败查询执行三次指数退避重试(retry),这意味着若你想测试一个出错的查询,测试很可能会超时。最简单的禁用重试方式是通过传给 VueQueryPlugin 的 QueryClient 进行配置。我们来扩展上面的例子:

TypeScript
1const queryClient = new QueryClient({2  defaultOptions: {3    queries: {4      // ✅ turns retries off5      retry: false,6    },7  },8})9 10const [result, wrapper] = withSetup(() => useCustomComposable(), queryClient)

这会将应用中所有查询的默认重试次数设为“零”。需要注意的是,该设置仅在你的实际 useQuery 未显式指定重试次数时才生效;如果某个查询明确设置了 5 次重试,该设置仍会优先生效,因为默认值仅作为后备选项(fallback)使用。

使用 Jest 将 gcTime 设为 Infinity

如果你使用 Jest,可将 gcTime 设为 Infinity,以避免出现 “Jest did not exit one second after the test run completed” 错误提示。此行为是服务端的默认设置;仅当你显式设置了 gcTime 时,才需要手动配置此项。

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

译自上游 59d11de