awesome-hacks
Docs

Jestによるテストの基礎

describe / it / expect / jest.fn() の基本をNestJS非依存のシンプルな例で押さえる

最終更新:2026/06/08

Jestは、JavaScript / TypeScriptでよく使われるテストランナー。NestJS・React・Node.jsスクリプトを問わず使える。この記事では、NestJSの設定に入る前に、Jestの基本的な書き方を押さえる。

この記事でやること

  • Jestをインストールして最初のテストを実行する
  • describe / it / expect の基本構造を理解する
  • beforeEach / afterEach でセットアップ・クリーンアップをする
  • jest.fn() で関数をモックする
  • 非同期コードをテストする
  • よく使うマッチャーを確認する

0. インストール

# pnpm
pnpm add -D jest @types/jest ts-jest

# npm
npm install -D jest @types/jest ts-jest

package.json に以下を追加する。

{
  "scripts": {
    "test": "jest"
  },
  "jest": {
    "preset": "ts-jest"
  }
}

すでにJestがある場合(nest new で作ったプロジェクトなど)はインストール不要。

1. テストファイルの基本構造

Jestはデフォルトで *.spec.ts / *.test.ts という名前のファイルをテストとして実行する。

// src/math.spec.ts

describe("add", () => {
  it("1 + 2 は 3 になる", () => {
    expect(1 + 2).toBe(3);
  });

  it("負の数でも動く", () => {
    expect(-1 + -2).toBe(-3);
  });
});
  • describe … テストをグループ化するブロック。クラス名や関数名を入れることが多い
  • it(または test … テストケースひとつ。ittest は同じ
  • expect … 値を検証する。toBe の部分を「マッチャー」と呼ぶ

pnpm test を実行すると、Jestはプロジェクト内の *.spec.ts*.test.ts という名前のファイルを再帰的に探して、まとめて実行する。src/math.spec.ts もその対象になる。

pnpm test
PASS  src/math.spec.ts
  add
    ✓ 1 + 2 は 3 になる (2 ms)
    ✓ 負の数でも動く (1 ms)

2. よく使うマッチャー

// src/matchers.spec.ts

describe("マッチャー早見", () => {
  it("プリミティブ値の比較", () => {
    expect(1 + 1).toBe(2);          // ===(完全一致)
    expect(null).toBeNull();
    expect(undefined).toBeUndefined();
    expect(true).toBeTruthy();
    expect(0).toBeFalsy();
  });

  it("オブジェクトや配列の比較", () => {
    expect({ a: 1 }).toEqual({ a: 1 });  // 深い比較(toBe は参照比較なので NG)
    expect([1, 2, 3]).toContain(2);
  });

  it("文字列の部分一致", () => {
    expect("hello world").toContain("world");
    expect("hello world").toMatch(/world/);
  });

  it("数値の範囲", () => {
    expect(3.14).toBeCloseTo(3.1, 1);   // 小数点以下1桁で近似比較
    expect(5).toBeGreaterThan(4);
    expect(3).toBeLessThanOrEqual(3);
  });

  it("例外のテスト", () => {
    const throwError = () => {
      throw new Error("oops");
    };
    expect(throwError).toThrow("oops");
  });
});

3. beforeEach / afterEach でセットアップとクリーンアップ

テストごとに共通の初期化・後処理が必要な場合に使う。

// src/counter.spec.ts

class Counter {
  private count = 0;
  increment() { this.count++; }
  get value() { return this.count; }
}

describe("Counter", () => {
  let counter: Counter;

  beforeEach(() => {
    counter = new Counter();  // 各テストの前に新しいインスタンスを作る
  });

  it("初期値は 0", () => {
    expect(counter.value).toBe(0);
  });

  it("increment すると 1 になる", () => {
    counter.increment();
    expect(counter.value).toBe(1);
  });

  it("2回 increment すると 2 になる", () => {
    counter.increment();
    counter.increment();
    expect(counter.value).toBe(2);
  });
});
  • beforeEach … 各テストのに実行
  • afterEach … 各テストのに実行(DBのクリーンアップなど)
  • beforeAlldescribe ブロック全体の最初に1回だけ実行
  • afterAlldescribe ブロック全体の最後に1回だけ実行

4. jest.fn() でモックを作る

外部依存(APIコール・DB・ファイルI/O)をテスト用の偽物に置き換えるときに使う。

// src/notifier.spec.ts

type Notifier = {
  send: (message: string) => void;
};

class UserService {
  constructor(private notifier: Notifier) {}

  register(name: string) {
    // 何かのビジネスロジック...
    this.notifier.send(`${name} さんが登録しました`);
  }
}

describe("UserService", () => {
  it("登録時に Notifier.send が呼ばれる", () => {
    const mockNotifier = { send: jest.fn() };  // 偽物の Notifier
    const service = new UserService(mockNotifier);

    service.register("Taro");

    // 呼ばれたか
    expect(mockNotifier.send).toHaveBeenCalled();
    // 何を渡して呼ばれたか
    expect(mockNotifier.send).toHaveBeenCalledWith("Taro さんが登録しました");
    // 何回呼ばれたか
    expect(mockNotifier.send).toHaveBeenCalledTimes(1);
  });
});

モックの戻り値を制御する

const mockFetch = jest.fn();

// 一度だけ返す値
mockFetch.mockResolvedValueOnce({ data: "hello" });

// 常に返す値
mockFetch.mockResolvedValue({ data: "always" });

// 一度だけ例外を投げる
mockFetch.mockRejectedValueOnce(new Error("network error"));

テスト間でモックをリセットする

beforeEach(() => {
  jest.clearAllMocks();  // 呼び出し履歴・戻り値をリセット
});

5. 非同期コードをテストする

// src/async.spec.ts

async function fetchUser(id: number): Promise<{ id: number; name: string }> {
  // 実際はAPIコールだが、ここではシミュレート
  if (id === 0) throw new Error("user not found");
  return { id, name: "Taro" };
}

describe("fetchUser", () => {
  it("ユーザーを返す", async () => {
    const user = await fetchUser(1);
    expect(user).toEqual({ id: 1, name: "Taro" });
  });

  it("id=0 のとき例外を投げる", async () => {
    await expect(fetchUser(0)).rejects.toThrow("user not found");
  });

  it("resolves で成功を検証する別の書き方", async () => {
    await expect(fetchUser(1)).resolves.toMatchObject({ name: "Taro" });
  });
});

非同期テストでは必ず async/await または return(Promiseを返す)を使う。忘れると、テストが終わる前にassertionが評価されず、常にPASSになってしまう。

6. 特定のテストだけ実行する

# ファイル名で絞り込む
pnpm test counter

# テスト名で絞り込む(-t フラグ)
pnpm test -- -t "increment"

# ファイルを --watch モードで実行
pnpm test -- --watch

「テスト名」とは、it() / describe()第1引数の文字列 のこと。

describe("Counter", () => {      // ← "Counter" が describe のテスト名
  it("increment すると 1 になる", () => {   // ← これが it のテスト名
    ...
  });
});

-t "increment" を指定すると、テスト名に "increment" を含む it だけが実行される。describe の名前で絞ることもでき、-t "Counter" ならその describe 内の it が全て対象になる。

it.only / describe.only でそのケース・グループだけ実行することもできる(コミット前に戻すのを忘れないこと)。

it.only("このテストだけ実行される", () => {
  expect(1).toBe(1);
});

7. カバレッジレポートを確認する

pnpm test -- --coverage
----------|---------|----------|---------|---------|
File      | % Stmts | % Branch | % Funcs | % Lines |
----------|---------|----------|---------|---------|
counter.ts|     100 |      100 |     100 |     100 |
----------|---------|----------|---------|---------|

カバレッジは「テストがコードを通過したか」であり、「ロジックが正しいか」ではない。高いカバレッジはバグがないことを保証しないが、テストが足りていない場所を発見する手がかりになる。

まとめ

要素役割
describeテストのグループ化
it / testテストケース1件
expect(...).toBe(...)値の検証
beforeEach各テスト前の初期化
afterEach各テスト後の後処理
jest.fn()偽物の関数を作る
mockResolvedValueOnceモックの戻り値を設定
toHaveBeenCalledWithモックの呼び出しを検証

次のステップ

NestJSプロジェクトでのテストは、NestJSのユニットテストNestJS E2Eテストで扱う。

参考