Dev Study
← 解説「ユニットテスト — Test.createTestingModule」に戻る

サンプルコードで身につける: ユニットテスト — Test.createTestingModule

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1テスト用モジュールを組み立てて取り出す

Test.createTestingModule({ providers }) でテスト専用の DIコンテナを作り、.compile() してから get で対象を取り出す、という最小の骨格です。本番の AppModule を起動せずにサービス単体を取り出せるのが要点です。

import { Test, TestingModule } from "@nestjs/testing";
import { CalcService } from "./calc.service";

describe("CalcService", () => {
  let service: CalcService;

  beforeEach(async () => {
    const moduleRef: TestingModule = await Test.createTestingModule({
      providers: [CalcService], // 依存のないサービスはそのまま
    }).compile();

    service = moduleRef.get<CalcService>(CalcService);
  });

  it("adds two numbers", () => {
    expect(service.add(2, 3)).toBe(5);
  });
});

2依存を useValue でモックに差し替える

テスト対象が依存するリポジトリを { provide, useValue: モック } で偽物に差し替える定番です。本物の DB に触れずに、モックの戻り値を起点にサービスのロジックだけを検証できます。

import { Test, TestingModule } from "@nestjs/testing";
import { UsersService } from "./users.service";
import { UsersRepository } from "./users.repository";

describe("UsersService", () => {
  let service: UsersService;
  const repoMock = { findOne: jest.fn() }; // 依存はモック

  beforeEach(async () => {
    const moduleRef: TestingModule = await Test.createTestingModule({
      providers: [
        UsersService, // テスト対象は本物
        { provide: UsersRepository, useValue: repoMock },
      ],
    }).compile();
    service = moduleRef.get<UsersService>(UsersService);
  });

  it("finds a user by id", async () => {
    repoMock.findOne.mockResolvedValue({ id: 1, name: "Alice" });
    const user = await service.findOne(1);
    expect(user.name).toBe("Alice");
  });
});

3「対象は本物・依存はモック」をプレーンTSで再現

ユニットテストの線引き「テスト対象そのものは本物、それが依存するものだけモック」を、デコレータなしで再現した実行例です。モックに仕込んだ戻り値を起点に、本物のサービスのロジックが検証されている様子が分かります。

TypeScript

4overrideProvider で一部だけ差し替える

既存モジュールの構成をそのまま流用しつつ、一部の依存だけを overrideProvider(Token).useValue(モック) で差し替える書き方です。providers をゼロから並べ直さずに済むため、依存の多い機能モジュールのテストで重宝します。

import { Test, TestingModule } from "@nestjs/testing";
import { UsersModule } from "./users.module";
import { MailService } from "./mail.service";
import { UsersService } from "./users.service";

describe("UsersService (module 流用)", () => {
  let service: UsersService;
  const mailMock = { send: jest.fn() };

  beforeEach(async () => {
    const moduleRef: TestingModule = await Test.createTestingModule({
      imports: [UsersModule], // 既存構成をそのまま使う
    })
      .overrideProvider(MailService) // メール送信だけモックに
      .useValue(mailMock)
      .compile();
    service = moduleRef.get(UsersService);
  });

  it("registers without sending real mail", async () => {
    await service.register("alice@example.com");
    expect(mailMock.send).toHaveBeenCalled();
  });
});

5実務: 外部依存をモックしたサービステスト

外部メール送信クライアントに依存するユーザー登録サービスを、クライアントをモックして検証する実務例です。本物のメールを送らずに「登録時に確認メールが送られたか」をモックの呼び出し履歴で確認するのが定石です。

import { Test, TestingModule } from "@nestjs/testing";
import { SignupService } from "./signup.service";
import { MailClient } from "./mail.client";
import { UsersRepository } from "./users.repository";

describe("SignupService", () => {
  let service: SignupService;
  const mailMock = { sendWelcome: jest.fn() };
  const repoMock = { save: jest.fn().mockResolvedValue({ id: 1 }) };

  beforeEach(async () => {
    const moduleRef: TestingModule = await Test.createTestingModule({
      providers: [
        SignupService, // 対象は本物
        { provide: MailClient, useValue: mailMock }, // 外部依存はモック
        { provide: UsersRepository, useValue: repoMock },
      ],
    }).compile();
    service = moduleRef.get(SignupService);
  });

  it("saves the user and sends a welcome mail", async () => {
    await service.signup("alice@example.com");
    expect(repoMock.save).toHaveBeenCalled();
    // 本物のメールは送らずに送信されたことだけ検証
    expect(mailMock.sendWelcome).toHaveBeenCalledWith("alice@example.com");
  });
});