测试有助于您编写和维护代码,并防止回归。测试框架可以帮助您完成这些任务,允许您描述关于代码应该如何行为的断言或期望。Svelte 对您使用的测试框架没有意见——您可以使用像 [Vitest](https://vitest.dev/)、[Jasmine](https://jasmine.github.io/)、[Cypress](https://www.cypress.io/) 和 [Playwright](https://playwright.dev/) 这样的解决方案编写单元测试、集成测试和端到端测试。 ## 单元和集成测试使用 Vitest 单元测试允许您测试代码的独立小部分。集成测试允许您测试应用程序的某些部分,以查看它们是否可以协同工作。如果您使用 Vite(包括通过 SvelteKit),我们建议使用[Vitest](https://vitest.dev/)。您可以使用 Svelte CLI 在项目创建期间或之后[设置 Vitest](/docs/cli/vitest)。 设置 Vitest 的手动步骤,首先安装它: ```bash npm install -D vitest ``` 然后调整你的 `vite.config.js`: ```js /// file: vite.config.js import { defineConfig } from +++'vitest/config'+++; export default defineConfig({ // ... // Tell Vitest to use the `browser` entry points in `package.json` files, even though it's running in Node resolve: process.env.VITEST ? { conditions: ['browser'] } : undefined }); ``` > \[!注意\] 如果不希望加载所有包的浏览器版本,因为(例如)您还测试后端库,[您可能需要求助于别名配置](https://github.com/testing-library/svelte-testing-library/issues/222#issuecomment-1909993331) 您现在可以为您的`.js/.ts`文件中的代码编写单元测试了: ```js /// file: multiplier.svelte.test.js import { flushSync } from 'svelte'; import { expect, test } from 'vitest'; import { multiplier } from './multiplier.svelte.js'; test('Multiplier', () => { let double = multiplier(0, 2); expect(double.value).toEqual(0); double.set(5); expect(double.value).toEqual(10); }); ``` ```js /// file: multiplier.svelte.js /** * @param {number} initial * @param {number} k */ export function multiplier(initial, k) { let count = $state(initial); return { get value() { return count * k; }, /** @param {number} c */ set: (c) => { count = c; } }; } ``` ### 使用您的测试文件中的符文 由于 Vitest 以与您的源文件相同的方式处理测试文件,只要文件名包含`.svelte`,您就可以在测试中使用 runes。 ```js /// file: multiplier.svelte.test.js import { flushSync } from 'svelte'; import { expect, test } from 'vitest'; import { multiplier } from './multiplier.svelte.js'; test('Multiplier', () => { let count = $state(0); let double = multiplier(() => count, 2); expect(double.value).toEqual(0); count = 5; expect(double.value).toEqual(10); }); ``` ```js /// file: multiplier.svelte.js /** * @param {() => number} getCount * @param {number} k */ export function multiplier(getCount, k) { return { get value() { return getCount() * k; } }; } ``` 如果被测试的代码使用了效果,您需要将测试包裹在`$effect.root`内: ```js /// file: logger.svelte.test.js import { flushSync } from 'svelte'; import { expect, test } from 'vitest'; import { logger } from './logger.svelte.js'; test('Effect', () => { const cleanup = $effect.root(() => { let count = $state(0); // logger uses an $effect to log updates of its input let log = logger(() => count); // effects normally run after a microtask, // use flushSync to execute all pending effects synchronously flushSync(); expect(log).toEqual([0]); count = 1; flushSync(); expect(log).toEqual([0, 1]); }); cleanup(); }); ``` ```js /// file: logger.svelte.js /** * @param {() => any} getValue */ export function logger(getValue) { /** @type {any[]} */ let log = []; $effect(() => { log.push(getValue()); }); return log; } ``` ### 组件测试 可以使用 Vitest 单独测试您的组件。 > \[注意\] 在编写组件测试之前,考虑你是否真的需要测试该组件,或者这更多的是关于组件内部的逻辑 *内部*。如果是这样,考虑提取出该逻辑以单独测试,而无需组件的额外开销。 要开始,安装 jsdom(一个模拟 DOM API 的库): ```bash npm install -D jsdom ``` 然后调整你的 `vite.config.js`: ```js /// file: vite.config.js import { defineConfig } from 'vitest/config'; export default defineConfig({ plugins: [ /* ... */ ], test: { // If you are testing components client-side, you need to setup a DOM environment. // If not all your files should have this environment, you can use a // `// @vitest-environment jsdom` comment at the top of the test files instead. environment: 'jsdom' }, // Tell Vitest to use the `browser` entry points in `package.json` files, even though it's running in Node resolve: process.env.VITEST ? { conditions: ['browser'] } : undefined }); ``` 之后,您可以创建一个测试文件,在其中导入组件进行测试,以编程方式与之交互,并编写关于结果的预期: ```js /// file: component.test.js import { flushSync, mount, unmount } from 'svelte'; import { expect, test } from 'vitest'; import Component from './Component.svelte'; test('Component', () => { // Instantiate the component using Svelte's `mount` API const component = mount(Component, { target: document.body, // `document` exists because of jsdom props: { initial: 0 } }); expect(document.body.innerHTML).toBe(''); // Click the button, then flush the changes so you can synchronously write expectations document.body.querySelector('button').click(); flushSync(); expect(document.body.innerHTML).toBe(''); // Remove the component from the DOM unmount(component); }); ``` 虽然这个过程非常直接,但它也是低级且有些脆弱的,因为您的组件的精确结构可能会频繁变化。像[@testing-library/svelte](https://testing-library.com/docs/svelte-testing-library/intro/)这样的工具可以帮助简化您的测试。上述测试可以重写如下: ```js /// file: component.test.js import { render, screen } from '@testing-library/svelte'; import userEvent from '@testing-library/user-event'; import { expect, test } from 'vitest'; import Component from './Component.svelte'; test('Component', async () => { const user = userEvent.setup(); render(Component); const button = screen.getByRole('button'); expect(button).toHaveTextContent(0); await user.click(button); expect(button).toHaveTextContent(1); }); ``` 在编写涉及双向绑定、上下文或片段属性的组件测试时,最好为您的特定测试创建一个包装组件并与它交互。`@testing-library/svelte` 包含一些 [示例](https://testing-library.com/docs/svelte-testing-library/example)。 ## 端到端测试使用 Playwright E2E(代表“端到端”)测试允许您从用户的角度测试您的完整应用程序。本节以[Playwright](https://playwright.dev/)为例,但您也可以使用其他解决方案,如[Cypress](https://www.cypress.io/)或[NightwatchJS](https://nightwatchjs.org/)。 您可以使用 Svelte CLI 在项目创建期间或之后设置 Playwright。您还可以使用`npm init playwright`进行设置。此外,您可能还想安装一个 IDE 插件,例如[VS Code 扩展,以便在 IDE 内部执行测试。](https://playwright.dev/docs/getting-started-vscode) 如果您已运行`npm init playwright`或未使用 Vite,您可能需要调整 Playwright 配置来告诉 Playwright 在运行测试之前要做什么 - 主要是在某个端口启动您的应用程序。例如: ```js /// file: playwright.config.js const config = { webServer: { command: 'npm run build && npm run preview', port: 4173 }, testDir: 'tests', testMatch: /(.+\.)?(test|spec)\.[jt]s/ }; export default config; ``` 您现在可以开始编写测试。这些测试完全不了解 Svelte 作为框架,所以您主要与 DOM 交互并编写断言。 ```js // @errors: 2307 7031 /// file: tests/hello-world.spec.js import { expect, test } from '@playwright/test'; test('home page has expected h1', async ({ page }) => { await page.goto('/'); await expect(page.locator('h1')).toBeVisible(); }); ```