概要
テストとは「動くかの確認」ではない。
それは**“コードが意図通りであることを検証し、将来の変更で壊れないことを保証する構造的契約”**である。
特にJavaScriptのように動的で柔軟な言語では、設計上の一貫性と構造の保証がテストによって初めて成立する。
本稿では、ユニット / 統合 / E2E テストの責務分離、依存隔離の技法、構造をテストしやすく保つための設計戦略を解説する。
1. テストの3レイヤーを明確に分ける
レイヤー | 目的 | テスト対象 |
---|---|---|
ユニット | 最小単位の動作検証 | 純粋関数 / 小ロジック |
統合 | 複数モジュール間の整合性確認 | API + 状態 + ロジック |
E2E | ユーザー視点での最終結果検証 | ブラウザ + 操作 |
- ✅ 各レイヤーで責務を明確に → 役割を混在させない設計が必要
2. テストしやすい構造の原則
// NG: 副作用を含む
function fetchUserAndSet() {
const res = await fetch('/api');
store.set(res.data);
}
// OK: 分離された構造
function parseUser(raw: any): User {
return { id: raw.id, name: raw.name };
}
- ✅ テストしたい処理は副作用から分離
- ✅ 状態更新・I/O は ユースケースやラッパー関数に限定
3. 依存を注入可能に設計する(DI構造)
const fetchUserUseCase = (api: UserApi) => async (id: string) => {
const user = await api.get(id);
return user.name;
};
- ✅ テストでは
api
をモックに差し替えるだけ → テスト容易性の担保 - ✅ 本番では
realApi
を注入 → 切り替え設計が柔軟
4. テスト対象を「構造単位」で分離する
❌ 1つの関数でデータ取得 → 変換 → 状態更新 → UI表示まで全て実行
✅ データ取得 → パース → 状態更新 を分割し、各関数をテスト可能に
// e.g.
function transformUser(data: ApiResponse): User { ... }
- ✅ 関数粒度の適切な分割により、各関数をユニットテスト可能に
5. テスト構造のディレクトリ戦略
src/
├─ usecases/
│ └─ loginUser.ts
├─ __tests__/
│ └─ usecases/
│ └─ loginUser.test.ts
- ✅ ファイル単位でペアにすることで 変更とテストの関係を明示
- ✅ コンポーネントごと or ユースケースごとに粒度を合わせる
6. ユースケース単位でテストを書く意義
// test: loginUser
it('ログイン成功時にセッションが保存される', async () => {
const deps = { authApi: mockApi, sessionStore: memoryStore };
const result = await loginUser(mockInput, deps);
expect(result.ok).toBe(true);
expect(memoryStore.set).toHaveBeenCalledWith(expect.anything());
});
- ✅ 「成功パス」+「失敗パス」+「例外パス」を明示的に網羅
- ✅ UIやAPIが変わっても ユースケースの設計と保証は維持できる
設計判断フロー
① この処理は分離できるか? → 副作用からピュアな関数に切り出す
② 依存は注入可能になっているか? → モックを差し替えられるか?
③ ユースケース単位で振る舞いをテストできているか?
④ テストレイヤーが混在していないか? → ユニット / 統合 / E2E を明確に
⑤ 結果の検証だけでなく、状態変化や呼び出しも追えているか?
よくあるミスと対策
❌ UIコンポーネント内で fetch → set → render 全部を直接書く
→ ✅ ユースケースにロジックを切り出し、テスト対象を構造的に抽出
❌ モック注入ができず、テストで毎回APIを叩いてしまう
→ ✅ 依存注入(DI)で柔軟に差し替え可能な設計へ
❌ 1テストで全体を検証しようとしてE2Eが巨大化
→ ✅ 単体テスト / 統合テスト / E2E を明確に分離し、役割を限定
結語
テストとは「動作確認」ではない。
それは**“構造の設計を検証し、変更に耐える保証を作るための安全戦略”**である。
- 責務ごとにテストレイヤーを分離し
- 副作用を避けて構造を分割し
- 依存注入でテスト性を確保し
- ユースケース単位でアプリの動作を保証する
JavaScriptにおけるテスト戦略とは、
“コードの信頼性と未来への自由を両立するための構造的設計”である。