Lesson 5

NestJSにおけるテスト

Lesson 5 Chapter 1
@nestjs/testingのインストール

NestJSでテストを記述するために@nestjs/testingをインストールします。

@nestjs/testingが入っている人

NestJSのプロジェクトを作成した時点で@nestjs/testingがすでに入っている人はこのChapterは飛ばして大丈夫です。次の「Chapter5.2 テストを書いて実行してみる」まで進みましょう。自分のプロジェクトに@nestjs/testingが入っているかは「package.json」を確認して見てみましょう。

@nestjs/testingのインストール

@nestjs/testingは以下のコマンドでインストールします。本番用のディレクトリに含めてたくないため-Dを付与してdevDependenciesへ入れます。

npm install -D @nestjs/testing

これで@nestjs/testingのインストールは完了です。次のChapterでは実際にテストを書いていきます。

Lesson 5 Chapter 2
テストを書いて実行してみる

このChapterでは実際にNestJSのテストを書いていきます。

テストの種類

NestJSのテストには以下の種類があります。

種類 説明
Unit Testing DIを使用せず、単一のクラスをテストする。対象のクラスを手動でインスタンス化し、対象のクラスが役割を満たすかをテストする。
Integration Testing クラスやモジュールの相互作用をテストする。実際にDIを使用し、ControllerとServiceやServiceとRepositoryは意図した関係で振る舞いを行えているかを確認する。
End-to-end Testing 実際にAppを使用するユーザーと本番システムとの相互関係をカバーするためのテスト。その相互関係をカバーすることでAppの要件を満たしているかを確認できる。

テストを書く準備

今回はUnit Testを書いていきます。Unit Testひとつの関数レベルから一番手軽に書けるテストなのでテストを書くにはまずUnit Testから始めるのがオススメです。今回は「todo.service.ts」のUnit Testを書いていきましょう。以下のコマンドでテスト用のファイルを作成します。

実務でのUnit Test

今回は単一のクラスや関数レベルでのUnit Testを行いますが、実務ではAPI単位でのUnit Testを行うことが多いです。理由としては関数やクラスを1つずつテストしていくと開発の工数に響いてしまうためです。テストの定義は現場によって異なる場合があるということは覚えておいてください。

Mac & Linux

~/nest-todo/src/services/todo
touch todo.service.spec.ts

Windows

~/nest-todo/src/services/todo
type todo.service.spec.ts

テストを書く

まずはひとつ簡単なテストコードを書いてみます。以下のコードをtodo.service.spec.tsに書き込みます。以下のテストコードはtruetrueであることをテストしています。当たり前のことを言ってますがまずはテストが通ることと書いてみることを目指してみます。

describe('todo.service.ts', () => {
  it('trueであること', () => {
    expect(true).toBe(true);
  });
});

上記コードを書いたら下記コマンドでテストを通してみます。

~/src
npm run test

すると以下の画像のようにテストがPASSしたことを確認できます。これが一連のテストの流れになります。

test_1.png テストがPASSたことを確認

describeとit

describeはテストのグループ化を行い、itはテストの内容を記述します。describeの中にitを記述することでテストのグループ化を行うことができます。

expectとtoBe

expectはテスト対象を引数にとり、toBeは期待する結果を引数に取ります。これらはJestの機能です。これらは基本的なテストの記述方法です。今後たくさん新しい知識が出てきますがその都度調べつつ使用していきましょう。

serviceのテスト

テストのための準備

テストの基本的な書き方がわかったところで「todo.service.ts」のテストを書いてみましょう。まずは下準備が必要なので下記をテストファイルに記述しましょう。コードの説明はコメントを参照してください。

todo.service.spec.ts
import { TestingModule, Test } from '@nestjs/testing';
import { TodoRepository } from 'src/repositories/todo/todo.repository';
import { TodoService } from './todo.service';

// Repositoryのメソッドをモック化
const mockTodoRepository = () => ({
  getAllTodos: jest.fn(),
});

// TodoServiceのためのテストグループを作成
describe('TodoServiceTest', () => {
  let service: TodoService;
  let repository: jest.Mocked<TodoRepository>;

  beforeEach(async () => {
    // テストのためのmoduleを作成
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        TodoService,
        {
          provide: TodoRepository,
          useFactory: mockTodoRepository,
        },
      ],
    }).compile();

    // モジュールから取得したものを代入
    service = module.get<TodoService>(TodoService);
    repository = module.get<TodoRepository>(
      TodoRepository,
    ) as jest.Mocked<TodoRepository>;
  });
});

serviceのテストに注力する

今回はserviceのテストを書くことに注力します。理由はserviceはビジネスロジックを担当しアプリケーションの要件を中心に扱うレイヤーだからだからです。

repositoryのモック化

また、repositoryをモック化しています。モック化とは実際の定義からテストコード上の定義に上書きすることを言います。これを行う理由としてはrepositoryのメソッドをモック化することでrepositoryのメソッドをテストすることなくserviceのテストに集中できるからです。また、repositoryは3層アーキテクチャのデータアクセス層を担うため実際の挙動をテストするためにはデータベースとの接続が必要になります。serviceの挙動をテストするという本来の目的とは逸れて、工数も嵩んでしまうため今回はモック化による簡略化でその懸念を回避しています。

serviceのテストを書く

それでは準備が済んだので以下のコードをテストコードとして書き込みましょう。

todo.service.spec.ts
describe('getAllTodos', () => {
    it('正常系:serviceの返り値が期待通りであること', async () => {
      const todos = [];
      // モック化したメソッドの戻り値を設定
      repository.getAllTodos.mockResolvedValue(todos);

      // 実行
      const result = await service.findAll();

      // 返り値が期待通りであることを確認
      expect(result).toEqual({ todos });
    });
  });

getAllTodosのテストグループ

describeでgetAllTodosのテストグループを作成しています。テストのグループ化はテストの可読性を高めるために行います。また、テストのグループ化によってテストの実行を絞り込むことができます。例えば、getAllTodosのテストグループだけを実行したい場合はnpm run test:watch -- -t getAllTodosと実行しgetAllTodosのテストのみwatchすることも可能です。

正常系のテスト

itで正常系:serviceの返り値が期待通りであることというテスト名をつけています。テスト名は日本語でも英語でもどちらでも構いません。「正常系」という言葉はテストの内容を表すものではなく、テストの目的を表すものです。「正常系」は挙動に異常がなく正常に動作する状態のことを指します。

テストを実行する

それではテストを実行してみましょう。以下のコマンドを実行してください。画像のようにテストが通っていれば成功です。

~/nest-todo/
npm run test

test_2.png getAllTodosのテストがパスしていることを確認

まとめ

今回はNestJSでユニットテストを書いてみました。ユニットテストはアプリケーションの品質を担保するために必要なテストです。また自動化していることで必要なタイミングで簡単に早く実行することができます。今回はNestJSでユニットテストを書く方法を紹介しましたが、他の言語でも同じようなテストの書き方ができます。また、ユニットテストは他のテストと比べて実行時間が短いため、テストの実行を絞り込むことができます。テストの実行を絞り込むことで、テストの実行時間を短縮することができます。アプリケーションの品質を担保するために積極的に取り入れるよう心がけていきましょう。