测试帮助编写和维护代码,防止回归。测试框架让你描述代码行为应满足的断言与预期。Svelte 不限定框架,可以用 Vitest、Jasmine、Cypress、Playwright 等编写单元、集成和端到端测试。
使用 Vitest 进行单元与组件测试
单元测试检查小而独立的代码片段,集成测试检查应用各部分能否协同工作。如果使用 Vite,包括 SvelteKit,推荐 Vitest。可在创建项目时或之后通过 Svelte CLI 配置。
手动安装:
npm install -D vitest
修改 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
});
如果不希望全部包都加载浏览器版本,例如还需要测试后端库,可能需要改用 alias 配置。
现在可以为 .js/.ts 中的代码编写测试。multiplier.svelte.test:
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);
});
multiplier.svelte 的 JavaScript 版本:
/**
* @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;
}
};
}
TypeScript 版本:
export function multiplier(initial: number, k: number) {
let count = $state(initial);
return {
get value() {
return count * k;
},
set: (c: number) => {
count = c;
}
};
}
在测试文件中使用 rune
Vitest 与源文件一样处理测试文件,因此文件名包含 .svelte 时,可以在测试中使用 rune:
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);
});
相应的 JavaScript 实现:
/**
* @param {() => number} getCount
* @param {number} k
*/
export function multiplier(getCount, k) {
return {
get value() {
return getCount() * k;
}
};
}
TypeScript 实现:
export function multiplier(getCount: () => number, k: number) {
return {
get value() {
return getCount() * k;
}
};
}
如果被测代码使用 effect,需要用 $effect.root 包围测试。logger.svelte.test:
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();
});
logger 的 JavaScript 实现:
/**
* @param {() => any} getValue
*/
export function logger(getValue) {
/** @type {any[]} */
let log = [];
$effect(() => {
log.push(getValue());
});
return log;
}
TypeScript 实现:
export function logger(getValue: () => any) {
let log: any[] = [];
$effect(() => {
log.push(getValue());
});
return log;
}
组件测试
可以单独渲染组件,在真实或模拟浏览器中模拟行为、执行断言,无需启动整个应用。
编写前,先想清楚需要测试的是组件本身,还是其中的逻辑。如果主要是逻辑,可以考虑提取后独立测试,避免组件开销。
先安装模拟 DOM API 的 jsdom:
npm install -D jsdom
修改 vite.config.js:
import { defineConfig } from 'vitest/config';
export default defineConfig({
plugins: [
/* ... */
],
test: {
// If you are testing components client-side, you need to set up 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
});
然后创建测试文件,导入组件,程序化交互,并断言结果:
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('<button>0</button>');
// Click the button, then flush the changes so you can synchronously write expectations
document.body.querySelector('button')?.click();
flushSync();
expect(document.body.innerHTML).toBe('<button>1</button>');
// Remove the component from the DOM
unmount(component);
});
流程直接,但较底层,也较脆弱,因为组件的精确结构可能经常变化。@testing-library/svelte 可以简化测试,前面的例子可改写为:
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);
});
涉及双向绑定、context 或 snippet props 时,最好为具体测试创建包装组件,再与其交互。@testing-library/svelte 文档提供相关示例。
使用 Storybook 测试组件
Storybook 用于开发和记录 UI 组件,也可以测试组件。它通过 Vitest 浏览器模式,在真实浏览器渲染,提供更接近实际的环境。
使用 Svelte CLI 的 npx sv add storybook 安装,并选择包含测试功能的推荐配置。如果已经使用 Storybook,或需要更多细节,参阅 Storybook 测试文档。
可以为组件不同状态创建 stories,并使用 play 函数,通过 Testing Library 和 Vitest API 模拟交互、编写断言。下面两个 story 分别显示空 LoginForm,以及模拟用户填写表单:
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import { expect, fn } from 'storybook/test';
import LoginForm from './LoginForm.svelte';
const { Story } = defineMeta({
component: LoginForm,
args: {
// Pass a mock function to the `onSubmit` prop
onSubmit: fn(),
}
});
</script>
<Story name="Empty Form" />
<Story
name="Filled Form"
play={async ({ args, canvas, userEvent }) => {
// Simulate a user filling out the form
await userEvent.type(canvas.getByTestId('email'), 'email@provider.com');
await userEvent.type(canvas.getByTestId('password'), 'a-random-password');
await userEvent.click(canvas.getByRole('button'));
// Run assertions
await expect(args.onSubmit).toHaveBeenCalledTimes(1);
await expect(canvas.getByText('You’re in!')).toBeInTheDocument();
}}
/>
使用 Playwright 进行端到端测试
E2E 测试从用户视角检查完整应用。本节以 Playwright 为例,也可选择 Cypress 或 NightwatchJS。
可以在创建项目时或之后通过 Svelte CLI 配置,也可运行 npm init playwright。安装 VS Code 等 IDE 扩展,还能直接从编辑器执行测试。
如果使用了 npm init playwright,或没有使用 Vite,可能需要修改配置,告诉 Playwright 测试前如何启动应用及使用哪个端口。例如:
const config = {
webServer: {
command: 'npm run build && npm run preview',
port: 4173
},
testDir: 'tests',
testMatch: /(.+\.)?(test|spec)\.[jt]s/
};
export default config;
现在可以编写测试。这些测试不需要了解 Svelte 框架,主要与 DOM 交互并执行断言。tests/hello-world.spec:
import { expect, test } from '@playwright/test';
test('home page has expected h1', async ({ page }) => {
await page.goto('/');
await expect(page.locator('h1')).toBeVisible();
});
原文:Testing。作者/维护方:Svelte 文档维护者。本文为中文翻译,代码及命令保留原文。











暂无评论内容