概要
API設計において、レスポンスの構造やエラーの表現は後回しにされやすい設計領域だ。
しかし、TDDを実践することで、戻り値・エラー・境界条件を「仕様」として最初に定義できるようになる。
本稿では、Node.js + Jest を使った TDD の流れを通して、
「振る舞いの設計」と「エラーの設計」をテスト駆動でどう構築するかを具体的に解説する。
1. API設計における「戻り値と例外」の責任
- APIは「成功」と「失敗」の2軸しかない
- それらを構造的にどう返すかが設計品質に直結する
TDDでは、「何を返すべきか」をテストという仕様定義の中で明文化できる。
2. 実装例:ユーザー登録API(成功 / バリデーションエラー / DB失敗)
🔴 Red:仕様を書く(Jest)
// userService.test.js
const { registerUser } = require('./userService');
describe('registerUser', () => {
it('should return userId on successful registration', async () => {
const user = { name: 'Alice', email: 'alice@example.com' };
const result = await registerUser(user);
expect(result).toEqual({ ok: true, userId: expect.any(String) });
});
it('should return validation error if email is missing', async () => {
const user = { name: 'Bob' };
const result = await registerUser(user);
expect(result).toEqual({
ok: false,
error: {
code: 'VALIDATION_ERROR',
message: 'Email is required.',
field: 'email',
},
});
});
it('should return system error if DB fails', async () => {
const user = { name: 'Charlie', email: 'fail@db.com' };
const result = await registerUser(user);
expect(result.ok).toBe(false);
expect(result.error.code).toBe('DB_ERROR');
});
});
3. 🟢 Green:仕様を通す実装を最小構成で書く
// userService.js
const crypto = require('crypto');
async function registerUser(user) {
if (!user.email) {
return {
ok: false,
error: {
code: 'VALIDATION_ERROR',
message: 'Email is required.',
field: 'email',
},
};
}
if (user.email === 'fail@db.com') {
return {
ok: false,
error: {
code: 'DB_ERROR',
message: 'Failed to connect to database',
},
};
}
return {
ok: true,
userId: crypto.randomUUID(),
};
}
module.exports = { registerUser };
4. 構造の意義:振る舞いを「定義」するという設計
- 成功:
{ ok: true, data }
- 失敗:
{ ok: false, error: { code, message, field? } }
このような構造をテストから先に書くことで、
- 振る舞いが明文化され、
- 実装はその仕様に忠実に従うよう設計される。
5. TDDによるAPI設計の価値
項目 | TDDでの設計効果 |
---|---|
戻り値構造 | 明示的かつ一貫性のある返却型が定義される |
エラー制御 | 曖昧な例外処理ではなく、構造化された失敗 |
拡張性 |
error.code ベースでロジックを追加できる |
UI実装側 | 型が保証され、安心してマッピング可能 |
設計判断フロー
① 成功の戻り値は何か? → テストで先に書いて定義する
② 失敗時に何を返したいか? → 構造・コード・メッセージを明示する
③ 想定する失敗の種類は? → バリデーション / システム / 認証 など
④ その情報でフロントは制御できるか? → UI要件も含めて考える
よくあるミスと対策
❌ 成功時だけを前提とした実装で例外を投げるだけ
→ ✅ 失敗も1つの仕様として明示的に定義する
❌ テストがレスポンス構造をチェックしていない
→ ✅ 「何を返すか」が設計の本質。型・構造を仕様として記述
❌ テストでtry/catchを使っている(=テストが仕様を覆っている)
→ ✅ catchではなく、構造的な result.ok === false
に寄せる
結語
TDDは、単に「コードを確認するもの」ではない。
それは**“振る舞いを先に定義し、実装をそれに従わせるための設計手法”**である。
- テストは「何を返すべきか」を先に定義する仕様書
- 失敗もレスポンスとして設計することで、APIが堅牢になる
- フロントエンドやクライアント実装との整合性が高まる
TDDとは、
“振る舞いの期待値をテストに刻み込み、構造的なAPI設計を導く戦略である。”