The blog post is published at Hashnode: Test Svelte Component Using Vitest & Playwright
Hi ๐, I'm David Peng. It's been a while since my last blog post.
In the last two months, I added 100+ unit/ component/ e2e tests in my Svelte project (yeah, I didn't do TDD because I wasn't familiar with testing enough ๐ ). I experimented with different test runners, kept measuring DX, and tried to find my ideal toolset for testing. I'd love to share some of my learnings and thoughts here.
In this article, I'll focus on the basic setup of Vitest & Playwright to test our Svelte components. You can check the demo repo here: Svelte Component Test Demo Repo
In my next blog post, I'll also write about the advanced component test and mocking (Svelte runtime module & networking). Stay tuned!
Here's a brief introduction of each type (my point of view):
You might hear more Svelte Developers migrating their tests from Jest to Vitest in the past few months.
Vitest is way faster than Jest because of Vite goodness, and it's a great fit with SvelteKit (use vite under the hood). Another plus is you can remove all your jest/ babel config ๐.
A great read here: Testing a Svelte app with Vitest
Playwright is a recommended E2E test runner in the Svelte community. You can also find Playwright as an option in create-svelte
.
Even though Vitest & Playwright are both test runners, they have a different focus and testing scenarios.
Vitest is incredibly fast because of its instant Hot Module Reload; it only reruns the test whenever the test, source code, or dependencies are changed, so it's suitable for the unit, integration tests.
While Playwright was created specifically for E2E testing, you need to build your Svelte app and start the server to test your app, which is much similar to the production.
But when it comes to the component test, we have a couple of choices:
Unit Test | โจ Component Test โจ | E2E Test |
---|---|---|
Vitest | vitest + @testing-library/svelte or @playwright/experimental-ct-svelte |
Playwright |
Let's see how to test the Svelte component using Vitest & Playwright.
Since we're going to talk about the component test, I like to share this clip from JS Party: [Twitter]accidentally testable, you can also listen to the episode #233
@testing-library/svelte
You can follow the below steps to setup Vitest or use the Svelte Adder: svelte-add-vitest
npm install -D vitest jsdom @testing-library/svelte
npm install -D @testing-library/jest-dom @types/testing-library__jest-dom
(optional but recommended)vite.config.js
:
```js
import { sveltekit } from '@sveltejs/kit/vite';/** @type {import('vite').UserConfig} / const config = { plugins: [sveltekit()], /* Add below settings / test: { // Jest like globals globals: true, environment: 'jsdom', include: ['src/**/.{test,spec}.ts'], // Extend jest-dom matchers setupFiles: ['./setupTest.js'] } };
export default config;
4. Create `setupTest.js` and extend `jest-dom` assertions/ matchers in Vitest:
```js
import matchers from '@testing-library/jest-dom/matchers';
import { expect, vi } from 'vitest';
expect.extend(matchers);
The reason to use
jest-dom
assertions can be found here: Common mistakes with React Testing Library #Using the wrong assertion
Add types
in tsconfig.json
:
{
"compilerOptions": {
"types": ["vitest/globals", "@testing-library/jest-dom"]
}
}
Create src/lib/Counter.test.ts
:
```ts
import { render, fireEvent, screen } from '@testing-library/svelte';
import Counter from './Counter.svelte';
describe('Test Counter.svelte', async () => { it('Initial counter should be 0', async () => { render(Counter); expect(screen.getByText('0')).toBeInTheDocument(); }); it('Test decrease', async () => { render(Counter); const decreaseButton = screen.getByLabelText('Decrease the counter by one'); // Decrease by two await fireEvent.click(decreaseButton); await fireEvent.click(decreaseButton); // Wait for animation const counter = await screen.findByText('-2'); expect(counter).toBeInTheDocument(); }); it('Test increase', async () => { render(Counter); const increaseButton = screen.getByLabelText('Increase the counter by one'); // Increase by one await fireEvent.click(increaseButton); // Wait for animation const counter = await screen.findByText('1'); expect(counter).toBeInTheDocument(); }); });
> The benefit of using `screen` is you no longer need to keep the render call destructure up-to-date as you add/remove the queries you need. You only need to type screen. And let your editor's magic autocomplete take care of the rest. (ref: [Common mistakes with React Testing Library #Not using `screen`](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#not-using-screen))
7. Add `test` script in `package.json`:
```json
{
"scripts": {
"test": "vitest"
}
}
This experimental component test is powered by Vite. For more details, please read the doc and the overview video from Playwright:
npm create svelte my-app
and choose the Demo Projectnpm install -D @playwright/experimental-ct-svelte
playwright-ct.config.ts
import type { PlaywrightTestConfig } from '@playwright/experimental-ct-svelte';
import { resolve } from 'node:path';
const config: PlaywrightTestConfig = {
testDir: 'tests/component',
use: {
ctViteConfig: {
resolve: {
alias: {
// Setup the built-in $lib alias in SvelteKit
$lib: resolve('src/lib')
}
}
}
}
};
export default config;
playwright/index.html
<html lang="en">
<body>
<div id="root"></div>
<script type="module" src="/playwright/index.js"></script>
</body>
</html>
playwright/index.js
// Apply theme here, add anything your component needs at runtime here.
// Here, we import the demo project's css file
import '../src/app.css';
tests/component/Counter.test.ts
@playwright/experimental-ct-svelte
wrap @playwright/test
to provide an additional built-in component-testing specific fixture called mount
import { test, expect } from '@playwright/experimental-ct-svelte';
import Counter from '$lib/Counter.svelte';
test('Test Counter.svelte', async ({ mount }) => {
const component = await mount(Counter);
// Initial counter is "0"
await expect(component).toContainText("0");
// Decrease the counter
await component.locator('[aria-label="Decrease the counter by one"]').dblclick();
await expect(component).toContainText('-2');
// Increase the counter
await component.locator('[aria-label="Increase the counter by one"]').click();
await expect(component).toContainText('-1');
});
Your VS Code may show an error like this:
We can solve this by adding include
in your tsconfig.json
:
{
"include": ["tests/**/*.ts"]
}
test:com
script in package.json
{
"scripts": {
"test:com": "playwright test -c playwright-ct.config.ts"
}
}
I haven't tried this combination in my projects, but you can check the example from the Vitest GitHub repo: Use Playwright with Vitest as test runner
I've spent a considerable amount of time writing component tests using both Vitest & Playwright. Here are some of my thoughts and a reflection of DX (developer experience):
In my opinion, Vitest + Testing Library would probably be a better choice at this moment. Despite that Vitest hasn't hit 1.0, my personal experience of migrating from Jest is pretty smooth. Also, Testing Library is stable and used by many companies.
Thank you for your reading. You can follow me on Twitter @davipon
Please leave your thoughts and experience below. Love to hear your feedback!
sveltejs/kit: Vitest for unit testing #5285
Shim SvelteKit runtime import aliases / Importing $app/* fails #1485