テスト戦略 レッスン5

テストダブル

モック・スタブ・フェイクなど、依存を置き換えてテストを容易にするテストダブルの種類と使い分けを学ぶ

なぜ依存を置き換える必要があるのか

外部APIやデータベース、現在時刻など「本物」に依存したままテストすると、 実行が遅くなったり、ネットワーク状況で結果が変わったり、料金が発生したりします。テストダブルは、これらの依存を本物に似せた代役に置き換える技術です。

代表的なテストダブルの種類

スタブ(Stub)

呼び出されると、あらかじめ決めた固定の値を返すだけの代役。

const weatherApiStub = {
  getTemperature: () => 25, // 常に25を返すだけ
};

モック(Mock)

値を返すだけでなく「正しい引数で呼ばれたか」「何回呼ばれたか」まで検証できる代役。

import { vi, expect } from "vitest";

const sendEmailMock = vi.fn();
await notifyUser(sendEmailMock, "user@example.com");

expect(sendEmailMock).toHaveBeenCalledWith("user@example.com");
expect(sendEmailMock).toHaveBeenCalledTimes(1);

フェイク(Fake)

本物と同じインターフェースを持ちつつ、簡易的な実装で動作する代役。 例: 実際のDBの代わりにメモリ上の配列でデータを管理するインメモリリポジトリ。

スパイ(Spy)

本物の実装を実行しつつ、呼び出し履歴だけを記録する代役。挙動を変えずに監視したいときに使う。

使い分けの考え方

「戻り値だけ確認したい」のか「呼び出され方まで検証したい」のかによって使い分けます。

// ① order-service.ts: 注文確定後にメール通知を送る処理
async function completeOrder(order, emailSender) {
  await saveOrder(order);
  await emailSender.send(order.userEmail, "ご注文ありがとうございます");
  return { status: "completed" };
}

// ② order-service.test.ts
it("注文確定後にユーザーへ通知メールを送る", async () => {
  const emailSenderMock = { send: vi.fn() };
  const order = { userEmail: "user@example.com" };

  await completeOrder(order, emailSenderMock);

  // 戻り値だけでなく「正しい相手に送られたか」まで検証する
  expect(emailSenderMock.send).toHaveBeenCalledWith(
    "user@example.com",
    expect.stringContaining("ありがとうございます")
  );
});

使いすぎへの注意

モックを多用しすぎると、内部の実装詳細に強く依存したテストになり、 リファクタリングのたびにテストが壊れやすくなります。 「本当に置き換える必要がある依存か」を都度検討しましょう。

ポイント

  • • テストダブルは外部依存を置き換え、テストを高速かつ決定的にする
  • • スタブは値を返すだけ、モックは呼ばれ方まで検証できる
  • • フェイクは簡易実装、スパイは本物を実行しつつ記録する
  • • モックの使いすぎは実装詳細への依存を生み、リファクタリング耐性を下げる

確認クイズ

1 / 3

スタブとモックの違いとして適切なものはどれか?