概要
テスタブルなコードとは、テストコードを書きやすいコードではなく、
**“構成可能であり、外部依存に左右されず、責務が明確な設計”**のことである。
その核心にあるのが「依存性注入(DI: Dependency Injection)」。
これは外部依存を直接呼び出すのではなく、「注入」される形で受け取ることで、
テスト可能性、柔軟性、保守性を飛躍的に高める。
本稿では、JavaScriptにおける 依存性注入の基本と実践的適用方法、設計判断基準、テスタビリティの担保戦略 を解説する。
1. 依存性注入の基礎概念
// 依存を内包している(密結合)
function fetchUser() {
return fetch('/api/user'); // ← 外部依存
}
// 依存を注入(疎結合)
function fetchUser(httpClient) {
return httpClient('/api/user');
}
- ✅ 依存先(fetch)を外部から受け取る
- ✅ テスト時に モック関数に差し替え可能
2. テスタブル構造の設計例
function createUserService(httpClient) {
return {
getUser: () => httpClient('/api/user'),
saveUser: (data) => httpClient('/api/user', { method: 'POST', body: JSON.stringify(data) })
};
}
- ✅ サービス構築関数として提供 → 依存注入可能なモジュール化
- ✅ 責務ごとに構造が分離されているため、単体テストが容易
3. 依存性の種類と注入方式
依存対象 | 例 | 注入方法 |
---|---|---|
外部API | fetch, axios | 引数 or DIコンテナ |
ストレージ | localStorage, DB | ラッパー関数の注入 |
ロガー | console.log, logger | ロガーオブジェクトの注入 |
設定値 | APIエンドポイントなど |
config オブジェクトで注入 |
4. DIと責務分離のベストプラクティス
function createApp({ httpClient, logger }) {
const service = createUserService(httpClient);
return {
run() {
service.getUser().then(user => {
logger.info('User loaded:', user);
});
}
};
}
- ✅ 構築関数に依存を渡す設計
- ✅
createApp()
は構成と依存を受け取り、実行に必要な責務のみを担う
5. テスト戦略:モックとの親和性
test('fetchUser calls correct endpoint', async () => {
const mockHttp = vi.fn().mockResolvedValue({ name: 'Taro' });
const service = createUserService(mockHttp);
const result = await service.getUser();
expect(result.name).toBe('Taro');
expect(mockHttp).toHaveBeenCalledWith('/api/user');
});
- ✅ 依存が注入されているため、モックでのテストがシンプル
- ✅ 振る舞いの確認がピュア関数的に行える
設計判断フロー
① この関数は外部依存(I/O、ストレージ、設定値)を含んでいるか?
② 依存はハードコードされているか? → 注入可能に変更
③ テストで依存を差し替えられるか? → ラッパー or ファクトリ化
④ 複数の責務を抱えていないか? → ロジックと構成を分離
⑤ DIを受け取る構造(createX)の粒度は適切か? → 責務単位で整理
よくあるミスと対策
❌ fetch()
や console.log()
を直接埋め込み、差し替え不能に
→ ✅ 依存性は関数やオブジェクトとして注入し、交換可能に
❌ クラスに依存を直接埋め込んでテスト困難に
→ ✅ コンストラクタ引数や create関数で構成時に注入
❌ 構成コードとロジックが一体化して再利用性が低下
→ ✅ createService → useService のように組み立てと使用を分離
結語
依存性注入とは、オブジェクト指向のためのパターンではない。
それは**“コードを構成可能にし、責務と依存の境界を明確に分離するための戦略”**である。
- I/Oやロガーなど環境依存は「注入」して扱う
- テスト可能性と保守性を上げるための前提として設計に取り入れる
- 最終的には「構成」と「ロジック」の明確な二分化へと繋がる
JavaScriptにおける依存性注入とは、
“実行環境と実装責務の境界を設計として明示する技術戦略である。”