Contents
Vitest を SvelteKit に導入するメリットとインストール手順
SvelteKit でコンポーネント単位のテストを書き始めるなら、まずは Vitest を選択します。
本セクションでは、Vitest が Vite と同じビルド基盤を共有している点や Jest ライクな API が提供されていることが、開発者にとってどのような利点になるかを解説し、実際のインストール手順をご紹介します。
Vitest の高速性とシンプルさ
Vitest は Vite のインクリメンタルビルド機構と ES モジュールのネイティブ実行をそのまま活用するため、テストプロセスの起動が数百ミリ秒程度で完了します(公式ベンチマーク参照)。この高速性に加えて、describe / it / expect といった Jest 互換 API が標準装備されているので、既存のテスト知識をそのまま移行できます。
npm/pnpm を使ったインストール手順
以下のコマンドで開発依存として必要なパッケージを一括インストールし、package.json にテスト用スクリプトを追加すれば設定は完了です。
|
1 2 3 4 5 6 |
# npm を利用する場合 npm install -D vitest @vitest/coverage-v8 @testing-library/svelte # pnpm を利用する場合 pnpm add -D vitest @vitest/coverage-v8 @testing-library/svelte |
package.json に次のスクリプトを追記します。
|
1 2 3 4 5 6 7 8 |
{ "scripts": { "test": "vitest", "test:watch": "vitest --watch", "test:cov": "vitest run --coverage" } } |
これだけでローカルでも CI 環境でも同一コマンドでテスト実行・カバレッジ取得が可能になります。
SvelteKit プロジェクトへの Vitest 設定
この章では、Vite の設定ファイル (vite.config.ts) とテスト専用の設定ファイル (vitest.config.ts) の役割を明確にしながら、SvelteKit 固有のエイリアスや SSR 環境がテストに与える影響について解説します。
Vite の設定ファイル vite.config.ts
vite.config.ts は ビルド全体 に関わる設定を記述する場所です。プラグインの登録、エイリアス、環境変数の定義などを行い、Vitest はこの設定を自動的に継承します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// vite.config.ts import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; export default defineConfig({ plugins: [sveltekit()], resolve: { alias: { $components: '/src/lib/components', $stores: '/src/lib/stores' } }, // Vite のビルド時にだけ有効な定義例 define: { __APP_ENV__: '"development"' } }); |
- ポイント:
environmentやテスト固有のオプションはここには記述しません。 - 注意点:SSR 用に Node の組み込みモジュール (
fs,path) をインポートしている場合、テスト実行時にモックが必要になることがあります。
テスト専用設定ファイル vitest.config.ts
vitest.config.ts は Vitest 固有 のオプションをまとめる場所です。Vite の設定は自動継承されますが、テストランナー固有の挙動(カバレッジやテスト対象ファイルのパターンなど)はここで指定します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// vitest.config.ts import { defineConfig } from 'vitest/config'; import svelte from '@sveltejs/vite-plugin-svelte'; export default defineConfig({ plugins: [svelte()], // .svelte のトランスパイルを有効化 test: { globals: true, // Jest ライクなグローバル API を使用 environment: 'jsdom', // DOM 操作が必要なコンポーネントテスト向け setupFiles: ['./vitest.setup.ts'], include: ['src/**/*.test.ts'], // TypeScript のみを対象にする例 coverage: { provider: 'v8', reporter: ['text', 'html'], thresholds: { lines: 80, functions: 75, branches: 70, statements: 80 } }, define: { __APP_ENV__: '"test"' } } }); |
- ポイント:
includeパターンはプロジェクトの言語に合わせて統一します(ここでは TypeScript のみ)。 - 役割の違い:
vite.config.tsがビルド全体を司るのに対し、vitest.config.tsはテスト実行時の挙動だけを制御する点をご留意ください。
@testing-library/svelte の基本 API と実装例
Testing Library 系は「ユーザーが実際に画面上で操作する姿」を再現することを第一理念としています。ここでは、Svelte コンポーネントテストで頻出する render, getBy* / queryBy*, fireEvent の使い方を具体例とともに紹介します。
render でコンポーネントを仮想 DOM にマウント
render は対象コンポーネントを JSDOM 上に展開し、テストコードから操作できるユーティリティオブジェクトを返します。第2引数は props の他に slots や context も渡せます。
|
1 2 3 4 5 6 7 8 |
import { render } from '@testing-library/svelte'; import Counter from '$components/Counter.svelte'; test('カウンタが初期値 0 を表示する', () => { const { getByText } = render(Counter, { props: { start: 0 } }); expect(getByText('Count: 0')).toBeInTheDocument(); }); |
getBy / queryBy 系クエリの選び方
テストが失敗したときに原因がすぐ分かるよう、役割 (role) やテキスト を優先して取得します。getBy* は要素が見つからないと例外をスローし、queryBy* は null を返すため「存在しないこと」を検証する際に便利です。
| クエリ | 用途 | 例 |
|---|---|---|
getByRole('button', { name: /increment/i }) |
アクセシビリティ属性を基準にボタン取得 | expect(getByRole('button')).toBeEnabled(); |
getByText('Loading…') |
テキストが必ず存在するケース | await waitFor(() => expect(getByText('Done')).toBeInTheDocument()); |
queryByTestId('error-msg') |
要素が 存在しない ことを確認 | expect(queryByTestId('error-msg')).not.toBeInTheDocument(); |
fireEvent でユーザー操作をシミュレート
fireEvent は DOM イベントを手動で発火させ、コンポーネント内部のハンドラが正しく動作するかを検証します。非同期処理が絡む場合は await を付け、必要に応じて waitFor や findBy* 系で結果を待ちます。
|
1 2 3 4 5 6 7 8 9 10 11 |
import { render, fireEvent } from '@testing-library/svelte'; import Counter from '$components/Counter.svelte'; test('ボタンクリックでカウントが増える', async () => { const { getByRole, getByText } = render(Counter); const incBtn = getByRole('button', { name: /increment/i }); await fireEvent.click(incBtn); expect(getByText('Count: 1')).toBeInTheDocument(); }); |
Svelte 固有機能のテスト実例
Svelte の特徴である props、カスタムイベント、slot、store はユニットテストでも必ず網羅すべき要素です。以下に TypeScript だけを対象とした最小限のコード例と期待するアサーションを書き出します。
props の受け渡し検証
|
1 2 3 4 5 6 7 8 |
import { render } from '@testing-library/svelte'; import Greeting from '$components/Greeting.svelte'; test('name プロパティが正しく表示される', () => { const { getByText } = render(Greeting, { props: { name: 'Alice' } }); expect(getByText('Hello, Alice!')).toBeInTheDocument(); }); |
カスタムイベントの捕捉
|
1 2 3 4 5 6 7 8 9 10 11 12 |
import { render, fireEvent } from '@testing-library/svelte'; import Modal from '$components/Modal.svelte'; test('閉じるボタンで close イベントが発火する', async () => { const handleClose = vi.fn(); const { getByRole, component } = render(Modal); component.$on('close', handleClose); await fireEvent.click(getByRole('button', { name: /close/i })); expect(handleClose).toHaveBeenCalledOnce(); }); |
slot のテスト
|
1 2 3 4 5 6 7 8 9 10 |
import { render } from '@testing-library/svelte'; import Card from '$components/Card.svelte'; test('slot 内のテキストが表示される', () => { const { getByText } = render(Card, { slots: { default: '<p>カード内容</p>' } }); expect(getByText('カード内容')).toBeInTheDocument(); }); |
store のリアクティブ更新検証
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import { render } from '@testing-library/svelte'; import CounterStore from '$stores/counter'; import CounterDisplay from '$components/CounterDisplay.svelte'; import { tick } from 'svelte'; test('store が更新されると UI が再描画される', async () => { const { getByText } = render(CounterDisplay); expect(getByText('Count: 0')).toBeInTheDocument(); CounterStore.update(n => n + 5); await tick(); // Svelte のリアクティビティが反映するまで待つ expect(getByText('Count: 5')).toBeInTheDocument(); }); |
高度なテストパターン・カバレッジ取得・CI への組み込み
実務プロジェクトでは非同期ロジックや CI 環境での自動テストが必須です。ここでは Promise/async の扱い方、waitFor/findBy* 系のベストプラクティス、カバレッジ設定と GitHub Actions への組み込み例を示します。
非同期コンポーネントのテスト
|
1 2 3 4 5 6 7 8 9 10 11 |
import { render, fireEvent } from '@testing-library/svelte'; import AsyncButton from '$components/AsyncButton.svelte'; test('クリック後に非同期で結果が表示される', async () => { const { getByRole, findByText } = render(AsyncButton); await fireEvent.click(getByRole('button')); // findBy* 系は内部で waitFor を実行し、Promise が解決するまでリトライ expect(await findByText('Finished')).toBeInTheDocument(); }); |
waitFor と findBy* の使い分け
- waitFor は手動で待機条件を記述したいときに使用。
- findBy* 系は「要素が出現するまで待つ」ケースのデフォルト実装として便利です。
|
1 2 3 4 |
await waitFor(() => { expect(getByText('Loaded')).toBeInTheDocument(); }, { timeout: 5000 }); // API が遅い場合はタイムアウトを延長 |
カバレッジレポートと GitHub Actions のサンプル workflow
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
name: CI on: push: branches: [main] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node uses: actions/setup-node@v4 with: node-version: '20' cache: pnpm - run: pnpm install - name: Run Vitest with coverage run: pnpm test:cov --reporter=dot - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} |
pnpm installのキャッシュにより CI が高速化します。- カバレッジしきい値は
vitest.config.tsで設定しているため、基準未達の場合はジョブが失敗します。
Vitest と Playwright の役割分担
| テスト種別 | 主な目的 | Vitest が得意とする領域 |
|---|---|---|
| ユニット / コンポーネント | ロジック・UI の正確性を高速に検証 | モックが容易、JSDOM で瞬時に実行 |
| E2E (Playwright) | アプリ全体のフローやブラウザ差異を確認 | 実ブラウザでのレンダリング、ネットワーク遅延シミュレーション |
|
1 2 3 4 5 6 7 |
{ "scripts": { "test:e2e": "playwright test", "test:all": "pnpm run test && pnpm run test:e2e" } } |
CI 上では npm run test:all を実行し、コンポーネントテストが失敗した段階で E2E がスキップされるように設定できます(GitHub Actions の continue-on-error オプション参照)。
JSDOM の制限とデバッグ手法
- CSS カスタムプロパティ は JSDOM では取得できません。スタイルロジックは Playwright に委譲するか、ユニットテストから除外してください。
- SSR フラグ が期待通りに評価されない場合は
vitest.config.tsのdefineオプションで明示的にフラグを設定します(例:__SVELTEKIT_SSR__: true)。
デバッグが必要なときは以下のコマンドで Node デバッガを起動できます。
|
1 2 |
pnpm vitest --inspect |
また、テストコード内に console.log や debugger; を埋め込むだけでも実行時にブレークポイントが取得可能です。
まとめ
- Vitest の選択理由:Vite と同一基盤を利用する高速起動と、Jest ライクな API により SvelteKit 開発フローへの適合性が高い。
- 設定のシンプルさ:
vite.config.tsがビルド全体、vitest.config.tsがテスト専用オプションを担う構造で、SSR・エイリアス・環境変数をそのまま利用できる。 - Testing Library で実装感の高いテスト:
render,getBy* / queryBy*,fireEventを組み合わせればユーザー視点に近いテストが簡潔に書ける。 - Svelte 固有機能も網羅:props、カスタムイベント、slot、store のテスト例を参考にすれば、実務で必要となるケースはほぼカバーできる。
- 高度なパターンと CI 連携:非同期処理のベストプラクティス、カバレッジ取得、GitHub Actions への自動化、Playwright との役割分担まで一通り揃えておけば、品質保証が自動化された開発フローを実現できる。
上記手順とベストプラクティスをプロジェクトに組み込むことで、Svelte コンポーネントのテストは高速かつ信頼性の高いものとなり、開発効率とコード品質の双方が向上します。