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

测试

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

Lit Query 通过响应式控制器(例如 createQueryController)实现功能,这些控制器附加到宿主元素上。控制器会从最近已连接的 QueryClientProvider 中读取 QueryClient,或从你作为其最后一个参数传入的 QueryClient 中读取,并在其宿主连接到文档时进行订阅。

因此,要测试一个查询,需将宿主元素连接到文档,等待元素更新,然后断言其渲染结果或控制器的返回值。

环境配置

本指南使用 Vitest 配合 jsdom(与 Lit Query 自身的测试方式一致),以及 jest-dom 匹配器。运行以下命令安装它们:

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

然后启用 jsdom,并添加一个配置文件,在其中加载匹配器并一次性注册 QueryClientProvider:

vite.config.ts

TypeScript
1import { defineConfig } from 'vitest/config'2 3export default defineConfig({4  test: {5    environment: 'jsdom',6    setupFiles: ['./vitest-setup.ts'],7  },8})

vitest-setup.ts

TypeScript
1import '@testing-library/jest-dom/vitest'2import { QueryClientProvider } from '@tanstack/lit-query'3 4customElements.define('query-client-provider', QueryClientProvider)

我们的第一项测试

假设有如下查询选项(与 queryOptions 共享),以及一个使用这些选项的元素:

queries.ts

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

greeting-view.ts

TypeScript
1import { LitElement, html } from 'lit'2import { createQueryController } from '@tanstack/lit-query'3import { greetingOptions } from './queries'4 5export class GreetingView extends LitElement {6  private readonly greeting = createQueryController(this, greetingOptions)7 8  render() {9    const query = this.greeting()10 11    if (query.isPending) return html`<p>Loading...</p>`12    if (query.isError) return html`<p>Error: ${query.error.message}</p>`13 14    return html`<p>${query.data}</p>`15  }16}17 18customElements.define('greeting-view', GreetingView)

我们可在 QueryClientProvider 下渲染该元素,并传入一个全新的 QueryClient;等待其首次更新,再等待数据完成渲染:

greeting-view.test.ts

TypeScript
1import { afterEach, expect, it, vi } from 'vitest'2import { QueryClient } from '@tanstack/lit-query'3import type { QueryClientProvider } from '@tanstack/lit-query'4import type { GreetingView } from './greeting-view'5import './greeting-view'6 7afterEach(() => {8  document.body.replaceChildren()9})10 11it('should render the greeting', async () => {12  const provider = document.createElement(13    'query-client-provider',14  ) as QueryClientProvider15  provider.client = new QueryClient()16  const view = document.createElement('greeting-view') as GreetingView17  provider.append(view)18  document.body.append(provider)19 20  await view.updateComplete21  expect(view.shadowRoot).toHaveTextContent('Loading...')22 23  await vi.waitFor(() => expect(view.shadowRoot).toHaveTextContent('Hello'))24})

在 afterEach 中移除元素会使它们断开连接,从而导致其控制器从 QueryClient 取消订阅。

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

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

单独测试控制器

若要在不渲染的情况下测试查询控制器,可将控制器附加到仅为测试而存在的宿主元素上,并将 QueryClient 作为其最后一个参数传入(而非渲染一个提供者)。然后对控制器的返回值进行断言:

TypeScript
1import { afterEach, expect, it, vi } from 'vitest'2import { LitElement } from 'lit'3import { QueryClient, createQueryController } from '@tanstack/lit-query'4import { greetingOptions } from './queries'5 6afterEach(() => {7  document.body.replaceChildren()8})9 10it('should fetch the greeting', async () => {11  const queryClient = new QueryClient()12 13  class TestHost extends LitElement {14    readonly greeting = createQueryController(15      this,16      greetingOptions,17      queryClient,18    )19  }20  customElements.define('test-host', TestHost)21 22  const host = new TestHost()23  document.body.append(host)24 25  await vi.waitFor(() => expect(host.greeting().isSuccess).toBe(true))26 27  expect(host.greeting().data).toBe('Hello')28})

自定义元素名称只能注册一次,因此请为每个测试宿主指定唯一名称。

模拟计时器

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

TypeScript
1import { afterEach, beforeEach, expect, it, vi } from 'vitest'2import { QueryClient } from '@tanstack/lit-query'3import type { QueryClientProvider } from '@tanstack/lit-query'4import type { GreetingView } from './greeting-view'5import './greeting-view'6 7beforeEach(() => {8  vi.useFakeTimers()9})10 11afterEach(() => {12  document.body.replaceChildren()13  vi.useRealTimers()14})15 16it('should render the greeting', async () => {17  const provider = document.createElement(18    'query-client-provider',19  ) as QueryClientProvider20  provider.client = new QueryClient()21  const view = document.createElement('greeting-view') as GreetingView22  provider.append(view)23  document.body.append(provider)24 25  await view.updateComplete26  expect(view.shadowRoot).toHaveTextContent('Loading...')27 28  await vi.advanceTimersByTimeAsync(10)29 30  expect(view.shadowRoot).toHaveTextContent('Hello')31})

关闭重试

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

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

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

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

译自上游 59d11de