测试
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:
1npm install @vue/test-utils --save-dev1import { 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 包:
1npm install @vue/test-utils@1 flush-promises --save-dev1import { 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。
我们的第一个测试
完成环境搭建后,即可编写一个简单测试。假设有如下自定义组合式函数:
1export function useCustomComposable() {2 return useQuery({ queryKey: ['customComposable'], queryFn: () => 'Hello' })3}我们可以按如下方式为其编写测试:
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 中恢复真实计时器,以确保即使断言失败,计时器也能被正确还原:
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:
1await vi.waitFor(() => expect(result.isSuccess.value).toBe(true))对于更复杂的组合式函数,直接测试使用它的组件可能更简便:以相同的插件配置挂载该组件,并对其渲染结果进行断言。
关闭重试机制
该库默认对失败查询执行三次指数退避重试(retry),这意味着若你想测试一个出错的查询,测试很可能会超时。最简单的禁用重试方式是通过传给 VueQueryPlugin 的 QueryClient 进行配置。我们来扩展上面的例子:
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 时,才需要手动配置此项。