テスト設計書
概要
MorningStatusApp のテストは Vitest で実行する。
テストコマンド: bun run test
テスト分類
| 分類 | 対象 | 配置場所 |
|---|---|---|
| ユニットテスト | ライブラリ関数・ユーティリティ | lib/*.test.ts |
| コンポーネントテスト | React コンポーネント(UI 表示・操作) | components/*.test.tsx |
| ページテスト | Next.js ページ(Server Component) | app/**/*.test.tsx |
| API テスト | Next.js API Route(GET/PUT) | app/api/**/*.test.ts |
| スクリプトテスト | バッチスクリプト(同期処理) | scripts/*.test.ts |
テストファイル一覧
ライブラリ(lib/)
| ファイル | テスト対象 | 主なテスト項目 |
|---|---|---|
lib/blob.test.ts |
getMembersFromBlob() |
Blob フェッチ成功・失敗・バリデーションエラー |
lib/releases.test.ts |
getReleases() |
Prisma フィールドマッピング・null→undefined 変換・TrackLink→ExternalLink |
lib/date.test.ts |
formatJST() |
JST 変換・空文字・無効値 |
lib/sns.test.ts |
resolveSnsCheck() / hasOfficialSnsSourceMatch() |
各 snsCheck 状態の解決・SNS ドメイン一致判定 |
lib/member-release-linker.test.ts |
linkMemberToReleases() |
在籍期間とリリース日の重複判定・overrides 適用 |
lib/musicbrainz.test.ts |
fetchReleaseGroupsByArtist() |
API 正常取得・レート制限(429)再試行・JSON 解析エラー |
lib/release-normalizer.test.ts |
normalizeReleases() / deduplicateReleases() |
正規化・重複排除・releaseDate のスペース処理 |
コンポーネント(components/)
| ファイル | テスト対象 | 主なテスト項目 |
|---|---|---|
components/MemberCard.test.tsx |
MemberCard |
氏名・世代・ステータス・SNS バッジ・最終更新日の表示 |
components/MemberDetailView.test.tsx |
MemberDetailView |
プロフィール表示・編集フォーム・保存・キャンセル・statusHistory 折りたたみ |
components/MemberSelect.test.tsx |
MemberSelect |
検索フィルタ(名前・読みの前方一致)・フィルタ解除 |
ページ(app/)
| ファイル | テスト対象 | 主なテスト項目 |
|---|---|---|
app/page.test.tsx |
Home(トップ) |
メンバー件数・最終更新日・バッチサマリ・isRecentlyUpdated 境界値 |
app/members/[id]/page.test.tsx |
MemberDetail |
メンバー表示・notFound・前後ナビゲーション・関連リリース表示 |
app/releases/page.test.tsx |
ReleasesPage |
フォーマット別グルーピング・データなし表示 |
app/releases/[id]/page.test.tsx |
ReleaseDetailPage |
リリース詳細表示・notFound・関連メンバー表示 |
API Route(app/api/)
| ファイル | テスト対象 | 主なテスト項目 |
|---|---|---|
app/api/members/route.test.ts |
GET / PUT /api/members |
Blob フェッチ・保存成功・MEMBERS_BLOB_URL 未設定エラー・statusHistory 蓄積・上限100件 |
スクリプト(scripts/)
| ファイル | テスト対象 | 主なテスト項目 |
|---|---|---|
scripts/sync-status.test.ts |
syncMembers() / fetchMemberStatus() / performSearch() |
SNS 検索・Web 検索・日本語判定・snsCheck 更新・active フラグ不変・スキップ・曜日制御・ドライラン |
scripts/sync-discography.test.ts |
syncDiscography() |
MusicBrainz 取得・正規化・Blob 保存・ドライラン |
scripts/migrate-to-blob.test.ts |
migrateToBlob() |
ローカルファイル読み込み・Blob 書き込み |
scripts/patch-official-sns.test.ts |
patchOfficialSns() |
officialSns マージ・型変換・mergedCount 0件 |
テスト実装指針
モックパターン
| 対象 | モック方法 |
|---|---|
fetch(グローバル) |
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({...})) |
| 環境変数 | vi.stubEnv('VAR_NAME', 'value') |
| Vercel Blob | vi.mock('@vercel/blob', () => ({ put: vi.fn() })) |
| exa-js(クラス) | vi.mock('exa-js', ...) + mockImplementation(function() {...}) |
Dateのみ偽装 |
vi.useFakeTimers({ toFake: ['Date'] }) + vi.setSystemTime(...) |
fetchモックには必ずok: trueを含めること(省略すると正常系が全失敗する)。
exa-js のようなクラスコンストラクタのモックは
functionキーワードを使うこと (アロー関数では「is not a constructor」エラーになる)。
クリーンアップ
afterEach(() => {
vi.unstubAllGlobals();
vi.unstubAllEnvs();
vi.useRealTimers(); // useFakeTimers を使った場合
// vi.restoreAllMocks() はタイマーを戻さないため useRealTimers が別途必要
});
Server Component のテスト
Server Component(async function Page())は render() を使わず直接 await して結果を検証する。
const result = await Home();
const html = JSON.stringify(result);
expect(html).toContain('"isRecentlyUpdated":true');
next/linkのLinkはJSON.stringifyで循環参照エラーになるためvi.mock('next/link', ...)で差し替えること。
曜日依存ロジックのテスト
曜日によって結果が変わるロジック(shouldSyncOG() 等)は Date を固定してテストする。
vi.useFakeTimers({ toFake: ['Date'] });
vi.setSystemTime(new Date('2026-01-05T11:30:00Z')); // 月曜日
再試行ロジックのテスト
setTimeout を含む再試行ループは vi.useFakeTimers() 全面適用 + vi.runAllTimersAsync() で完走させる。
テスト実行
bun run test # 全テスト実行
bun run test --watch # ウォッチモード
bun run test path/to/file.test.ts # 特定ファイルのみ
CI では
bun run lint(0 warnings)・bun run type-checkも合わせて通過させること。