3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TDDで進めるAPI設計:戻り値と例外をテストから設計する

Posted at

概要

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設計を導く戦略である。”

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?