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

测试

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

Svelte Query 通过函数(如 createQuery)来工作,这些函数可以是官方提供的,也可以是你自己封装的、在其基础上进行包装的自定义函数。它们会从 Svelte 上下文中读取 QueryClient(由 QueryClientProvider 设置),或从你作为最后一个参数传入的 QueryClient 访问器中读取。

遵循 Svelte 文档的测试指南,你可以在 $effect.root 中测试这些函数(位于 .svelte.test.ts 文件内),并使用 Svelte Testing Library 测试组件。

设置

运行以下命令安装 Vitest、jsdom 和 Svelte Testing Library:

Shell
1npm install vitest jsdom @testing-library/svelte @testing-library/jest-dom --save-dev

然后将 svelteTesting 插件及用于 jest-dom 匹配器的设置文件添加到你的 Vite 配置中:

vite.config.ts

TypeScript
1import { svelte } from '@sveltejs/vite-plugin-svelte'2import { svelteTesting } from '@testing-library/svelte/vite'3import { defineConfig } from 'vitest/config'4 5export default defineConfig({6  plugins: [svelte(), svelteTesting()],7  test: {8    environment: 'jsdom',9    setupFiles: ['./vitest-setup.ts'],10  },11})

vitest-setup.ts

TypeScript
1import '@testing-library/jest-dom/vitest'

在 SvelteKit 项目中,请改用 sveltekit() 插件(来自 @sveltejs/kit/vite),而非 svelte()。

我们的第一项测试

假设有如下查询选项(与 queryOptions 共享):

queries.ts

TypeScript
1import { queryOptions } from '@tanstack/svelte-query'2 3export const greetingOptions = queryOptions({4  queryKey: ['greeting'],5  queryFn: () =>6    new Promise<string>((resolve) => setTimeout(() => resolve('Hello'), 10)),7})

由于 Svelte 上下文仅存在于组件内部,因此需将一个全新的 QueryClient 作为第二个参数传给 createQuery。在 $effect.root 中创建查询,等待其成功完成,并在 finally 中清理 effect root,以确保即使断言失败也能完成清理:

queries.svelte.test.ts

TypeScript
1import { expect, it, vi } from 'vitest'2import { QueryClient, createQuery } from '@tanstack/svelte-query'3import { greetingOptions } from './queries'4import type { CreateQueryResult } from '@tanstack/svelte-query'5 6it('should fetch the greeting', async () => {7  const queryClient = new QueryClient()8  let query!: CreateQueryResult<string>9  const cleanup = $effect.root(() => {10    query = createQuery(11      () => greetingOptions,12      () => queryClient,13    )14  })15 16  try {17    await vi.waitFor(() => expect(query.isSuccess).toBe(true))18 19    expect(query.data).toBe('Hello')20  } finally {21    cleanup()22  }23})

为避免在每个测试中重复编写 $effect.root 及其清理逻辑,你也可以用一个辅助函数将整个测试体包裹起来,使其在 effect root 内运行:

TypeScript
1import { expect, it, vi } from 'vitest'2import { QueryClient, createQuery } from '@tanstack/svelte-query'3import { greetingOptions } from './queries'4 5function withEffectRoot(fn: () => Promise<void>) {6  return async () => {7    let promise!: Promise<void>8    const cleanup = $effect.root(() => {9      promise = fn()10    })11    try {12      await promise13    } finally {14      cleanup()15    }16  }17}18 19it(20  'should fetch the greeting',21  withEffectRoot(async () => {22    const queryClient = new QueryClient()23    const query = createQuery(24      () => greetingOptions,25      () => queryClient,26    )27 28    await vi.waitFor(() => expect(query.isSuccess).toBe(true))29 30    expect(query.data).toBe('Hello')31  }),32)

注意:我们为每个测试都创建了一个新的 QueryClient。这有助于确保测试完全与其他测试隔离。

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

query 是响应式的,因此应在等待之后再读取其属性,而不是在一开始就对其进行解构。解构得到的值会保持其被读取时的快照值。

测试组件

组件从 QueryClientProvider 所设置的上下文中读取 QueryClient。假设有一个使用相同查询选项的组件:

Greeting.svelte

svelte
1<script lang="ts">2  import { createQuery } from '@tanstack/svelte-query'3  import { greetingOptions } from './queries'4 5  const query = createQuery(() => greetingOptions)6</script>7 8{#if query.isPending}9  <p>Loading...</p>10{:else if query.isError}11  <p>Error: {query.error.message}</p>12{:else}13  <p>{query.data}</p>14{/if}

在仅为测试而存在的组件中,用 QueryClientProvider 将其包裹:

GreetingTest.svelte

svelte
1<script lang="ts">2  import { QueryClientProvider } from '@tanstack/svelte-query'3  import type { QueryClient } from '@tanstack/svelte-query'4  import Greeting from './Greeting.svelte'5 6  let { queryClient }: { queryClient: QueryClient } = $props()7</script>8 9<QueryClientProvider client={queryClient}>10  <Greeting />11</QueryClientProvider>

然后使用一个全新的 QueryClient 渲染它,并等待数据渲染完成:

Greeting.svelte.test.ts

TypeScript
1import { render } from '@testing-library/svelte'2import { expect, it } from 'vitest'3import { QueryClient } from '@tanstack/svelte-query'4import GreetingTest from './GreetingTest.svelte'5 6it('should render the greeting', async () => {7  const rendered = render(GreetingTest, {8    props: { queryClient: new QueryClient() },9  })10 11  expect(rendered.getByText('Loading...')).toBeInTheDocument()12  expect(await rendered.findByText('Hello')).toBeInTheDocument()13})

模拟计时器

vi.waitFor 和 findByText 会持续重试直到断言通过,这对等待真实网络请求的查询函数有效。当查询函数依赖计时器时,你可以改用模拟计时器并主动推进它,例如借助 vi.advanceTimersByTimeAsync。请在 afterEach 中恢复真实计时器,以确保即使断言失败也能恢复:

TypeScript
1import { render } from '@testing-library/svelte'2import { afterEach, beforeEach, expect, it, vi } from 'vitest'3import { QueryClient } from '@tanstack/svelte-query'4import GreetingTest from './GreetingTest.svelte'5 6beforeEach(() => {7  vi.useFakeTimers()8})9 10afterEach(() => {11  vi.useRealTimers()12})13 14it('should render the greeting', async () => {15  const rendered = render(GreetingTest, {16    props: { queryClient: new QueryClient() },17  })18 19  expect(rendered.getByText('Loading...')).toBeInTheDocument()20 21  await vi.advanceTimersByTimeAsync(10)22 23  expect(rendered.getByText('Hello')).toBeInTheDocument()24})

关闭重试

该库默认启用三次指数退避重试,这意味着如果你希望测试一个出错的查询,测试很可能会超时。最简单的关闭重试方式是通过你在测试中使用的 QueryClient:

TypeScript
1const queryClient = new QueryClient({2  defaultOptions: {3    queries: {4      // ✅ turns retries off5      retry: false,6    },7  },8})

这会将所有使用该客户端的查询的默认重试策略设为“不重试”。需要注意的是,该设置仅在你实际的 createQuery 未显式指定重试次数时才生效。例如,若某个查询明确设置了 5 次重试,则该设置仍会优先于默认值,因为默认值仅作为后备方案使用。

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

译自上游 59d11de