Vitest + SWC テスト環境
なぜ Vitest を使うのか
Jest と比較して、Vitest には以下の利点があります。
| 観点 | Jest | Vitest |
|---|---|---|
| 起動速度 | 5〜10 秒 | 500 ミリ秒〜1 秒 |
| ESModule 対応 | 設定が複雑 | ネイティブサポート |
| トランスパイル | ts-jest(遅い) | SWC(高速) |
| 設定ファイル | jest.config.js + babel.config | vitest.config.ts のみ |
| Watch モード | 遅い | 高速 |
特に ESModule 環境では、Jest の設定が複雑になりがちですが、Vitest ではシンプルに対応できます。
テストの種類
テストは 2 種類に分離しています。
| テスト種別 | ファイルパターン | 説明 |
|---|---|---|
| ユニットテスト | *.test.ts |
単体機能のテスト |
| E2E テスト | *.spec.ts |
エンドツーエンドテスト |
この分離により、目的に応じたテスト実行が可能です。
# ユニットテストのみ実行
pnpm vitest run --project unit
# E2E テストのみ実行
pnpm vitest run --project e2e
# 全テスト実行
pnpm vitest run
SWC による高速トランスパイル
ビルドシステムと同様に、テスト環境でも SWC を使用してデコレーターを変換しています。
// vitest.config.ts
import 'reflect-metadata';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import swc from 'unplugin-swc';
import { defineConfig } from 'vitest/config';
const dirname = path.dirname(fileURLToPath(import.meta.url));
const swcPlugin = swc.vite({
sourceMaps: true,
jsc: {
parser: {
syntax: 'typescript',
decorators: true,
dynamicImport: true,
},
transform: {
legacyDecorator: true,
decoratorMetadata: true,
},
target: 'es2023',
keepClassNames: true,
},
module: { type: 'es6' },
minify: false,
});
ポイント:
unplugin-swcを使用して Vite/Vitest と統合- esbuild プラグインと同じ SWC 設定(
decoratorMetadata: true) sourceMaps: trueでデバッグ時のスタックトレースを改善
Vitest 設定の全体像
// vitest.config.ts
const resolveAlias = {
alias: {
$domains: path.resolve(dirname, 'src/domains'),
$shared: path.resolve(dirname, 'src/shared'),
},
};
export default defineConfig({
test: {
projects: [
{
plugins: [swcPlugin],
resolve: resolveAlias,
test: {
name: 'unit',
globals: true,
environment: 'node',
include: ['src/**/*.test.ts'],
},
},
{
plugins: [swcPlugin],
resolve: resolveAlias,
test: {
name: 'e2e',
globals: true,
environment: 'node',
include: ['src/**/*.spec.ts'],
},
},
],
coverage: {
provider: 'v8',
reportsDirectory: './coverage',
reporter: ['text', 'json', 'lcov'],
include: ['src/**/*.ts'],
exclude: [
'src/**/*.dto.ts',
'src/**/index.ts',
'src/**/*.test.ts',
'src/**/*.spec.ts',
'src/main.ts',
'src/**/*.module.ts',
],
},
},
resolve: resolveAlias,
});
設定のポイント
projects によるテスト分離
projects 配列を使用して、ユニットテストと E2E テストを分離しています。それぞれに異なる設定を適用できます。
projects: [
{
test: {
name: 'unit',
include: ['src/**/*.test.ts'],
},
},
{
test: {
name: 'e2e',
include: ['src/**/*.spec.ts'],
},
},
],
パスエイリアスの設定
resolve.alias でパスエイリアスを設定します。tsconfig.json の paths と一致させます。
const resolveAlias = {
alias: {
$domains: path.resolve(dirname, 'src/domains'),
$shared: path.resolve(dirname, 'src/shared'),
},
};
globals: true
describe, it, expect などのグローバル関数を有効化します。インポート不要でテストを記述できます。
// globals: true の場合
describe('UserService', () => {
it('should return user', () => {
expect(result).toBeDefined();
});
});
// globals: false の場合(インポート必要)
import { describe, it, expect } from 'vitest';
V8 カバレッジ
V8 エンジンのネイティブカバレッジ機能を使用しており、高速にカバレッジレポートを生成できます。
coverage: {
provider: 'v8',
reportsDirectory: './coverage',
reporter: ['text', 'json', 'lcov'],
include: ['src/**/*.ts'],
exclude: [
'src/**/*.dto.ts',
'src/**/index.ts',
'src/**/*.test.ts',
'src/**/*.spec.ts',
'src/main.ts',
'src/**/*.module.ts',
],
},
除外対象
以下のファイルはカバレッジから除外しています。
| ファイル | 理由 |
|---|---|
*.dto.ts |
データ定義のみ、ロジックなし |
index.ts |
再エクスポートのみ |
*.test.ts |
テストファイル自体 |
*.spec.ts |
テストファイル自体 |
main.ts |
エントリーポイント |
*.module.ts |
NestJS モジュール定義のみ |
カバレッジ実行
# カバレッジ付きでテスト実行
pnpm test:coverage
package.json のスクリプト設定
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"test:unit": "vitest run --project unit",
"test:e2e": "vitest run --project e2e"
}
}
Jest からの移行
Jest から Vitest への移行は比較的簡単です。
API の互換性
Vitest は Jest とほぼ同じ API を提供しています。
// Jest も Vitest も同じ
describe('UserService', () => {
beforeEach(() => {
// setup
});
it('should return user', () => {
expect(result).toEqual({ id: 1, name: 'John' });
});
});
主な違い
| 機能 | Jest | Vitest |
|---|---|---|
| モック | jest.fn() |
vi.fn() |
| タイマー | jest.useFakeTimers() |
vi.useFakeTimers() |
| スパイ | jest.spyOn() |
vi.spyOn() |
| モジュールモック | jest.mock() |
vi.mock() |
jest を vi に置き換えるだけで、ほとんどのテストが動作します。
まとめ
Vitest + SWC の組み合わせにより、以下を実現しています。
- 高速な起動 - ESModule ネイティブで起動が速い
- NestJS デコレーターのサポート - SWC による高速トランスパイル
- テストの分離 - ユニット/E2E テストを個別に実行可能
- V8 カバレッジ - 高速なカバレッジレポート生成
- Jest との互換性 - 移行コストが低い
次のページでは、TypeScript と ESLint の設定について解説します。