Ateam Group U-30 Advent Calendar 2022の1日目は
株式会社エイチームライフデザインの@oekazumaが担当します。
はじめに
Svelte、SvelteKitのプロジェクトにおいて、コンポーネントテストを書きたい場合、どのようなテストランナーを使ってどう書けばいいかを解説していきます。
SvelteKitが用意してくれているプロジェクト作成のCLIではPlaywrightとVitestがデフォルトで追加できるようになっていますのでこれらのテストランナーを使ったコンポーネントテストを紹介します。
今回、作ったサンプルはこのリポジトリで確認できます。
SvelteKitとは
SvelteをベースとしたハイパフォーマンスなWebアプリを作るためのフレームワークです。
ReactでいうNext.js、VueでいうNuxt.jsような立ち位置だと認識していただければわかりやすいかと思います。
2022/12/1 現在の開発ステータスは1.0のRC版がリリースされている状態で、正式な1.0がリリースされる日も近いでしょう。
Vitestとは
Vite環境で動作する高速なテストフレームワークで、Jestの代替として期待されているフレームワークです。
SvelteKitはViteを使用しているので簡単な設定のみで利用可能になり、相性が抜群です。
Playwrightとは
Microsoftが開発しているE2Eテストランナーです。対応しているブラウザ数が多かったり、実行速度が他のE2Eテストランナーと比べて速いことが特徴で、Svelteコミュニティで推奨されています。
環境
- Node
- v18.12.0
- npm
- v8.19.2
- @sveltejs/kit
- v1.0.0-next.567
- svelte
- v3.53.1
- vitest
- v0.25.3
- @testing-library/svelte
- v3.2.2
- jsdom
- v20.0.3
- @playwright/test
- v1.28.1
- @playwright/experimental-ct-svelte
- v1.28.1
新規プロジェクトを作成
SvelteKitの新規プロジェクトをCLIを使って作成します。
$ npm create svelte@latest svelte-component-test-vitest-playwright
今回はTypeScript、Playwright, Vitestを選択してプロジェクトを作成しました。
✔ Which Svelte app template? › Skeleton project
✔ Add type checking with TypeScript? › Yes, using TypeScript syntax
✔ Add ESLint for code linting? … No
✔ Add Prettier for code formatting? … No
✔ Add Playwright for browser testing? … Yes
✔ Add Vitest for unit testing? … Yes
Vitest + Testing Library
必要となるパッケージを追加でインストールします。
$ npm install -D @testing-library/svelte jsdom
vite.config.js
とtsconfig.json
の構成を以下のように変更します。
import { sveltekit } from '@sveltejs/kit/vite';
/** @type {import('vite').UserConfig} */
const config = {
plugins: [sveltekit()],
test: {
include: ['src/**/*.{test,spec}.{js,ts}'],
+ globals: true,
+ environment: 'jsdom'
}
};
export default config;
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
+ "types": ["vitest/globals"]
}
}
src/lib
配下にテスト対象のコンポーネントを用意します。
+
をクリックすると数字が1増え、-
をクリックすると数字が1減る、Counterコンポーネントを今回はテストします。
<script lang="ts">
import { spring } from 'svelte/motion';
let count = 0;
const displayed_count = spring();
$: displayed_count.set(count);
$: offset = modulo($displayed_count, 1);
function modulo(n: number, m: number) {
return ((n % m) + m) % m;
}
</script>
<div class="counter">
<button on:click={() => (count -= 1)} aria-label="Decrease the counter by one">
-
</button>
<div class="counter-viewport">
<div class="counter-digits" style="transform: translate(0, {100 * offset}%)">
<strong class="hidden" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
<strong>{Math.floor($displayed_count)}</strong>
</div>
</div>
<button on:click={() => (count += 1)} aria-label="Increase the counter by one">
+
</button>
</div>
<style>
.counter {
display: flex;
border-top: 1px solid rgba(0, 0, 0, 0.1);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
margin: 1rem 0;
}
.counter button {
width: 2em;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
border: 0;
background-color: transparent;
touch-action: manipulation;
color: var(--text-color);
font-size: 2rem;
}
.counter button:hover {
background-color: var(--secondary-color);
}
.counter-viewport {
width: 8em;
height: 4em;
overflow: hidden;
text-align: center;
position: relative;
}
.counter-viewport strong {
position: absolute;
display: flex;
width: 100%;
height: 100%;
font-weight: 400;
color: var(--accent-color);
font-size: 4rem;
align-items: center;
justify-content: center;
}
.counter-digits {
position: absolute;
width: 100%;
height: 100%;
}
.hidden {
top: -100%;
user-select: none;
}
</style>
src/lib
配下にテストコードを追加します。
import { render, fireEvent, screen } from '@testing-library/svelte';
import Counter from './Counter.svelte';
describe('Counter.svelte', async () => {
test('カウンターの初期値は0', async () => {
render(Counter);
expect(screen.getByText('0')).toBeTruthy();
});
test('減算処理', async () => {
render(Counter);
const decreaseButton = screen.getByLabelText('Decrease the counter by one');
await fireEvent.click(decreaseButton);
await fireEvent.click(decreaseButton);
const counter = await screen.findByText('-2');
expect(counter).toBeTruthy();
});
test('加算処理', async () => {
render(Counter);
const increaseButton = screen.getByLabelText('Increase the counter by one');
await fireEvent.click(increaseButton);
const counter = await screen.findByText('1');
expect(counter).toBeTruthy();
});
});
package.json
にテストスクリプトを追加します。
今回はvitestとplaywrightを両方使うのであえてわかりやすい名前にしています。
{
"name": "svelte-component-test-vitest-playwright",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
- "test": "playwright test"
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
- "test:unit": "vitest",
+ "test:vitest": "vitest",
+ "test:playwright": "playwright test"
},
"devDependencies": {
"@playwright/test": "^1.28.1",
"@sveltejs/adapter-auto": "^1.0.0-next.90",
"@sveltejs/kit": "^1.0.0-next.567",
"@testing-library/svelte": "^3.2.2",
"jsdom": "^20.0.3",
"svelte": "^3.53.1",
"svelte-check": "^2.9.2",
"svelte-preprocess": "^4.10.7",
"tslib": "^2.4.1",
"typescript": "^4.9.3",
"vite": "^3.2.4",
"vitest": "^0.25.3"
},
"type": "module"
}
実際にテストを動かしてみましょう。
$ npm run test:vitest
無事動きました
Playwright Components Tests
Playwrightはコンポーネントテストを実験的にサポートしています。
必要となるパッケージがあるので追加でインストールします。
$ npm 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: {
$lib: resolve('src/lib')
}
}
}
}
};
export default config;
実行に必要なファイルを追加します。
<html lang="en">
<body>
<div id="root"></div>
<script type="module" src="/playwright/index.ts"></script>
</body>
</html>
// 例えば、app.cssのような、コンポーネントの実行時に必要なファイルをimportします。
// 今回は不要なのでファイルのみ作成してコメントアウトしています。
// import '../src/app.css';
テストコードを追加します。
import { test, expect } from '@playwright/experimental-ct-svelte';
import Counter from '$lib/Counter.svelte';
test('Counter.svelte', async ({ mount }) => {
const component = await mount(Counter);
await expect(component).toContainText("0");
await component.locator('[aria-label="Decrease the counter by one"]').dblclick();
await expect(component).toContainText('-2');
await component.locator('[aria-label="Increase the counter by one"]').click();
await expect(component).toContainText('-1');
});
package.json
にテストスクリプトを追加します。
今回はE2Eテストについては特には触れませんが、E2Eテストのスクリプトと分けておくと便利です。
{
"name": "svelte-component-test-vitest-playwright",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"test:vitest": "vitest",
+ "test:playwright-ct": "playwright test -c playwright-ct.config.ts",
- "test:playwright": "playwright test",
+ "test:playwright-e2e": "playwright test",
},
"devDependencies": {
"@playwright/experimental-ct-svelte": "^1.28.1",
"@playwright/test": "^1.28.1",
"@sveltejs/adapter-auto": "^1.0.0-next.90",
"@sveltejs/kit": "^1.0.0-next.567",
"@testing-library/svelte": "^3.2.2",
"jsdom": "^20.0.3",
"svelte": "^3.53.1",
"svelte-check": "^2.9.2",
"svelte-preprocess": "^4.10.7",
"tslib": "^2.4.1",
"typescript": "^4.9.3",
"vite": "^3.2.4",
"vitest": "^0.25.3"
},
"type": "module"
}
実際にテストを動かしてみましょう。
$ npm run test:playwright-ct
無事動きました
まとめ
Vitest + Testing Library
-
メリット
- 実行速度がPlaywrightに比べて速い。
- Testing Libraryは採用事例も多く、安定している。
-
デメリット
- セットアップや依存関係がやや複雑。
- Vitestの構文、Testing Libraryの構文の両方を学ばないといけない。
Playwright Components Tests
-
メリット
- ブラウザ上で実行するのでより本番環境に近い状態でテストができる。
- Playwrightの構文が使えるので追加で学ぶことが少ない。
-
デメリット
- 実行速度がVitest + Testing Libraryに比べて遅い。
- まだまだ実験段階で不安定である。
どちらを選ぶべきか
個人的には現時点では、Vitest + Testing Libraryを選ぶほうがいいと思います。
コンポーネントテストをどこまで重視するかにもよりますが、Vitest + Testing Libraryの構成は安定していますし、Playwrightに比べて実行速度も速いので開発体験がいいです。
現状、僕がSvelteKitプロジェクトでどの領域のテストに対してどのテストランナーを選ぶか問われると下記のように回答します。
ユニットテスト | コンポーネントテスト | E2Eテスト |
---|---|---|
Vitest | Vitest + Testing Library | Playwright |
コンポーネントテストの箇所はPlaywright、StoryBookなどの選択肢もあると思いますし、何を担保したいかによって変わると思いますので状況に応じて最適な状態を模索していきたいですね。