JestのクラスのモックをTypeScriptで使用する

JestのクラスのモックをTypeScriptで使用する

TypeScriptのプロジェクトで、Jestを使ってテストを書いているのですが、基本的なクラスのモックの使い方や、モックのメソッドに渡された引数のチェックのしかたが最初わからなかったので、動作するようになった結果をメモ。

前提として、Service層とRepository層にレイヤ分けされた構造において、Repository層のクラスをモックにして、Service層のテストをしたい場合を想定しています。

TypeScriptにおけるJestのクラスのモックの使い方

公式ドキュメントに記載の通り、jest.mock('./モック化したいクラスのパス')と記載することで、指定したクラスをモック化できます。ただ、これを記載しただけだとすべてのメソッドを、常に undefined を返すモック関数に置き換えるので、メソッド呼び出しを行うテストでは、mockImplementation()メソッドを使って、メソッドの動作を指定する必要があります。

この時、公式ドキュメントの通り、以下の記載を行おうとすると、SoundPlayer.mockImplementationの箇所でTypeScriptに怒られます(Property ‘mockImplementation’ does not exist on type)。

import SoundPlayer from './sound-player';
jest.mock('./sound-player');

describe('テスト', () => {
  beforeAll(() => {
    SoundPlayer.mockImplementation(() => {
      return {
        playSoundFile: () => {
          return "test"
        },
      };
    });
  });
});

これについては、SoundPlayer as jest.Mockのように、Mock型にキャストすることで回避しました(もしかするともっと良い方法があるのかもしれません)。

import SoundPlayer from './sound-player';
jest.mock('./sound-player');
const SoundPlayerMock = SoundPlayer as jest.Mock; // 追加

describe('テスト', () => {
  beforeAll(() => {
    // SoundPlayerMockに変更
    SoundPlayerMock.mockImplementation(() => {
      return {
        playSoundFile: () => {
          return "test"
        },
      };
    });
  });
});

モックのメソッドに渡された引数のチェックのしかた

次は、モックをスパイする方法についてです。公式ドキュメントに以下の記載があるのですが、私はこれを試してみてもダメでした。

メソッドの呼び出しは theAutomaticMock.mock.instances[index].methodName.mock.calls に保存されます。

どうしたかというと、以下のようにmockImplementationメソッドで定義する関数を、モック関数で定義するようにしました。

const mockFunc = jest.fn(() => {
    return "test";
});

mockImplementationの呼び出し箇所を以下に修正

SoundPlayerMock.mockImplementation(() => {
      return {
        playSoundFile: mockFunc,
      };
});

こうすることで、mockFunc.mock.calls[0][0]で、モック関数に渡された引数を取得することができました。モック関数の使い方の詳細はこちらを参照ください。