Skip to content

Commit dba4dfa

Browse files
dummdidummRich-Harrisbenmccann
authored
docs: add testing section (#12600)
* docs: add testing section - explain component and rune testing using Vitest - explain e2e testing using Playwright closes #10244 closes #10650 * better examples * Update documentation/docs/05-misc/02-testing.md Co-authored-by: Rich Harris <[email protected]> * Update documentation/docs/05-misc/02-testing.md Co-authored-by: Rich Harris <[email protected]> * Update documentation/docs/05-misc/02-testing.md Co-authored-by: Rich Harris <[email protected]> * fix * Update documentation/docs/05-misc/02-testing.md * Update documentation/docs/05-misc/02-testing.md Co-authored-by: Ben McCann <[email protected]> * Update documentation/docs/05-misc/02-testing.md * we normally use single quotes * Apply suggestions from code review Co-authored-by: Rich Harris <[email protected]> * more details on component testing * extract component testing into its own sub section, reorder a bit * fix code example * Update documentation/docs/05-misc/02-testing.md * Apply suggestions from code review Co-authored-by: Rich Harris <[email protected]> --------- Co-authored-by: Rich Harris <[email protected]> Co-authored-by: Ben McCann <[email protected]>
1 parent 1d17677 commit dba4dfa

File tree

1 file changed

+214
-4
lines changed

1 file changed

+214
-4
lines changed

documentation/docs/05-misc/02-testing.md

Lines changed: 214 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,217 @@
22
title: Testing
33
---
44

5-
- component testing basics
6-
- rune testing basics
7-
- vitest setup
8-
- e2e
5+
Testing helps you write and maintain your code and guard against regressions. Testing frameworks help you with that, allowing you to describe assertions or expectations about how your code should behave. Svelte is unopinionated about which testing framework you use — you can write unit tests, integration tests, and end-to-end tests using solutions like [Vitest](https://vitest.dev/), [Jasmine](https://jasmine.github.io/), [Cypress](https://www.cypress.io/) and [Playwright](https://playwright.dev/).
6+
7+
## Unit and integration testing using Vitest
8+
9+
Unit tests allow you to test small isolated parts of your code. Integration tests allow you to test parts of your application to see if they work together. If you're using Vite (including via SvelteKit), we recommend using [Vitest](https://vitest.dev/).
10+
11+
To get started, install Vitest:
12+
13+
```bash
14+
npm install -D vitest
15+
```
16+
17+
Then adjust your `vite.config.js`:
18+
19+
```diff
20+
/// file: vite.config.js
21+
- import { defineConfig } from 'vite';
22+
+ import { defineConfig } from 'vitest/config';
23+
24+
export default defineConfig({ /* ... */ })
25+
```
26+
27+
You can now write unit tests for code inside your `.js/.ts` files:
28+
29+
```js
30+
/// file: multiplier.svelte.test.js
31+
import { flushSync } from 'svelte';
32+
import { expect, test } from 'vitest';
33+
import { multiplier } from './multiplier.js';
34+
35+
test('Multiplier', () => {
36+
let double = multiplier(0, 2);
37+
38+
expect(double.value).toEqual(0);
39+
40+
double.set(5);
41+
42+
expect(double.value).toEqual(10);
43+
});
44+
```
45+
46+
### Using runes inside your test files
47+
48+
It is possible to use runes inside your test files. First ensure your bundler knows to route the file through the Svelte compiler before running the test by adding `.svelte` to the filename (e.g `multiplier.svelte.test.js`). After that, you can use runes inside your tests.
49+
50+
```js
51+
/// file: multiplier.svelte.test.js
52+
import { flushSync } from 'svelte';
53+
import { expect, test } from 'vitest';
54+
import { multiplier } from './multiplier.svelte.js';
55+
56+
test('Multiplier', () => {
57+
let count = $state(0);
58+
let double = multiplier(() => count, 2);
59+
60+
expect(double.value).toEqual(0);
61+
62+
count = 5;
63+
64+
expect(double.value).toEqual(10);
65+
});
66+
```
67+
68+
If the code being tested uses effects, you need to wrap the test inside `$effect.root`:
69+
70+
```js
71+
/// file: logger.svelte.test.js
72+
import { flushSync } from 'svelte';
73+
import { expect, test } from 'vitest';
74+
import { logger } from './logger.svelte.js';
75+
76+
test('Effect', () => {
77+
const cleanup = $effect.root(() => {
78+
let count = $state(0);
79+
80+
// logger uses an $effect to log updates of its input
81+
let log = logger(() => count);
82+
83+
// effects normally run after a microtask,
84+
// use flushSync to execute all pending effects synchronously
85+
flushSync();
86+
expect(log.value).toEqual([0]);
87+
88+
count = 1;
89+
flushSync();
90+
91+
expect(log.value).toEqual([0, 1]);
92+
});
93+
94+
cleanup();
95+
});
96+
```
97+
98+
### Component testing
99+
100+
It is possible to test your components in isolation using Vitest.
101+
102+
> Before writing component tests, think about whether you actually need to test the component, or if it's more about the logic _inside_ the component. If so, consider extracting out that logic to test it in isolation, without the overhead of a component
103+
104+
To get started, install jsdom (a library that shims DOM APIs):
105+
106+
```bash
107+
npm install -D jsdom
108+
```
109+
110+
Then adjust your `vite.config.js`:
111+
112+
```js
113+
/// file: vite.config.js
114+
import { defineConfig } from 'vitest/config';
115+
116+
export default defineConfig({
117+
plugins: [
118+
/* ... */
119+
],
120+
test: {
121+
// If you are testing components client-side, you need to setup a DOM environment.
122+
// If not all your files should have this environment, you can use a
123+
// `// @vitest-environment jsdom` comment at the top of the test files instead.
124+
environment: 'jsdom'
125+
},
126+
// Tell Vitest to use the `browser` entry points in `package.json` files, even though it's running in Node
127+
resolve: process.env.VITEST
128+
? {
129+
conditions: ['browser']
130+
}
131+
: undefined
132+
});
133+
```
134+
135+
After that, you can create a test file in which you import the component to test, interact with it programmatically and write expectations about the results:
136+
137+
```js
138+
/// file: component.test.js
139+
import { flushSync, mount, unmount } from 'svelte';
140+
import { expect, test } from 'vitest';
141+
import Component from './Component.svelte';
142+
143+
test('Component', () => {
144+
// Instantiate the component using Svelte's `mount` API
145+
const component = mount(Component, {
146+
target: document.body, // `document` exists because of jsdom
147+
props: { initial: 0 }
148+
});
149+
150+
expect(document.body.innerHTML).toBe('<button>0</button>');
151+
152+
// Click the button, then flush the changes so you can synchronously write expectations
153+
document.body.querySelector('button').click();
154+
flushSync();
155+
156+
expect(document.body.innerHTML).toBe('<button>1</button>');
157+
158+
// Remove the component from the DOM
159+
unmount(component);
160+
});
161+
```
162+
163+
While the process is very straightforward, it is also low level and somewhat brittle, as the precise structure of your component may change frequently. Tools like [@testing-library/svelte](https://testing-library.com/docs/svelte-testing-library/intro/) can help streamline your tests. The above test could be rewritten like this:
164+
165+
```js
166+
/// file: component.test.js
167+
import { render, screen } from '@testing-library/svelte';
168+
import userEvent from '@testing-library/user-event';
169+
import { expect, test } from 'vitest';
170+
import Component from './Component.svelte';
171+
172+
test('Component', async () => {
173+
const user = userEvent.setup();
174+
render(Component);
175+
176+
const button = screen.getByRole('button');
177+
expect(button).toHaveTextContent(0);
178+
179+
await user.click(button);
180+
expect(button).toHaveTextContent(1);
181+
});
182+
```
183+
184+
When writing component tests that involve two-way bindings, context or snippet props, it's best to create a wrapper component for your specific test and interact with that. `@testing-library/svelte` contains some [examples](https://testing-library.com/docs/svelte-testing-library/example).
185+
186+
## E2E tests using Playwright
187+
188+
E2E (short for 'end to end') tests allow you to test your full application through the eyes of the user. This section uses [Playwright](https://playwright.dev/) as an example, but you can also use other solutions like [Cypress](https://www.cypress.io/) or [NightwatchJS](https://nightwatchjs.org/).
189+
190+
To get start with Playwright, either let you guide by [their VS Code extension](https://playwright.dev/docs/getting-started-vscode), or install it from the command line using `npm init playwright`. It is also part of the setup CLI when you run `npm create svelte`.
191+
192+
After you've done that, you should have a `tests` folder and a Playwright config. You may need to adjust that config to tell Playwright what to do before running the tests - mainly starting your application at a certain port:
193+
194+
```js
195+
/// file: playwright.config.js
196+
const config = {
197+
webServer: {
198+
command: 'npm run build && npm run preview',
199+
port: 4173
200+
},
201+
testDir: 'tests',
202+
testMatch: /(.+\.)?(test|spec)\.[jt]s/
203+
};
204+
205+
export default config;
206+
```
207+
208+
You can now start writing tests. These are totally unaware of Svelte as a framework, so you mainly interact with the DOM and write assertions.
209+
210+
```js
211+
/// file: tests/hello-world.spec.js
212+
import { expect, test } from '@playwright/test';
213+
214+
test('home page has expected h1', async ({ page }) => {
215+
await page.goto('/');
216+
await expect(page.locator('h1')).toBeVisible();
217+
});
218+
```

0 commit comments

Comments
 (0)