JavaScript
reactjs
jest
TISDay 6

Javascriptテストツール"Jest"のMockを使ってみた

初めてのQiita投稿(かつ、アドベントカレンダー初参加)で遅刻をしてしまうという大チョンボをやらかしてしまいました...
この記事は新米エンジニアがJavaScriptのテストを行うにあたってハマった、JestのMock機能について調べた記事です。

はじめに

皆さんはJavaScriptのテストをする際、どのようなツールを使っていますか。
JavaScriptのテストでは、複数のテストツールを組み合わせてテストをすることが多いです。
以下がよく用いられるテストツール/フレームワークのようです。

  • テストフレームワーク
    • mocha
    • Jasmine
    • Jest
  • テストランナー
    • Karma
  • アサーションツール
    • power-assert
    • chai
  • テストダブル
    • Sinon.js

(上記のツール/フレームワークは、上記で分類した機能に特化しているわけではなく重複した機能を持つものもあります。)
そんなJavaScriptのテストツールの1つである"Jest"と、"Jest"のMock機能を使ってみたので紹介をします。

本記事で扱うこと

  • 簡単な"Jest"の紹介
  • 初級レベルのMock機能の紹介

本記事で扱わないこと

以下は、公式や別記事での記載も多いため割愛します。
(今後、別記事として記載したら紹介します。)

  • 導入方法
  • アサーションの紹介
  • UI Testやスナップショットテスト

Jestとは

Jest 公式
Facebook製のJavascriptテストツールで、Reactと相性が良いです。
公式にも"Zero configuration testing platform"とあるように、configを書かずにテストが実施できる便利なツールです。
(設定が必要な場合は、対話式で設定ファイルが作成できるjest --initも用意されています)
’vue’や’Angular’でも利用できるプラグインもあるため、JSフレームワークによらないテスト実装が可能です。

従来のフレームワークと比べてJestを使うメリットは、以下が挙げられます。
- アサーションやテストダブルなどが用意されているオールインワンなツールであるため、ライブラリの依存関係がシンプル
- JSDOMのエミュレータ上でテストを実施できるため、テスト実行が高速
- watchオプションを使うことで、差分のみのテストやfailしたケースに絞ってテストを実施できる
- カバレッジ出力も、オプションで指定するだけで標準出力可能(設定ファイルで外部ファイルへの出力も可能!)

今回は、こんな素敵なテストツール"Jest"のMock機能を紹介します。

JestのFunction Mock

JestのMock機能を使うことで、実際の処理に必要なFunctionをMockFunctionに差し替えることができるようになります。
例えば、userIdをキーにしてバックエンドシステムからfetchでUser情報を取得する様な下記の様な機能があったとします。

// userApi.js
export const getUser = async (id) => {
  return await fetch(`https://example.com/user?userId=${id}`, {
    method: 'GET',
  });
};

export const updateUserBySendUserDataResponse = (user, data) => {
  // ビジネスモデルの更新処理
  // ...
}
// userService.js
import {getUser} from './userApi';

export const getUserAsync = async (id) => {
  const response = await getUser(id).catch(error => (error));
  // ビジネスモデルへの変換等
  return response;
};

この様なService層のテストを実施する際、テストの成否がバックエンドシステムなどの外部コンポーネントの状況に依存する様な不安定なテストにしたくはありません。そのような場合、APIの機能をMock化する必要があります。
JestのMock Functionを使えば、以下の様にAPIのfunctionをmock化できます。そうすることで、Serviceのテストは外部コンポーネントの状態に依存せず実施することが可能になります。

// userService.test.js
import * as api from './userApi';
import {getUserAsync} from './userSerivce';
import ErrorMessage from './constants/errorMessage';

describe('Test getUserAsync', () => {
  describe('正常系', () => {
    beforeEach(() => {
      // getUserをMock化
        api.getUser = jest.fn(async (id) => {
          return {
            userId: id,
            name: `name-${id}`,
          };
        });
      },
      5000
    );
    test('正しくデータが取得できること', async () => {
        const req = 'hoge';
        const expected = {userId: 'hoge', name: 'name-hoge'};
        const actual = await getUserAsync(req);
        expect(expected).toEqual(actual);
            // mock functionの機能を検証
        const mockFn = api.getUser;
        expect(mockFn).toHaveBeenCalledTimes(1);
      },
      10000
    );
  });
  describe('異常系', () => {
    beforeEach(() => {
        api.getUser = jest.fn(async (id) => {
          throw new Error(ErrorMessage.NotFound);
        });
      },
      5000
    );
    test('エラーが返却されること', async () => {
        const req = 'hoga';
        const error = new Error(ErrorMessage.NotFound);
        const actual = await getUserAsync(req);
        expect(error).toEqual(actual);
            // mock functionの機能を検証
        const mockFn = api.getUser;
        expect(mockFn).toHaveBeenCalled();
      },
      10000
    );
  });
});

setupとteardownもdiscribe単位で記述できるため、正常系と異常系でmockFunctionの内容を書き換えるといったことも可能です。

まとめ

Jestを使うことでシンプルに、外部コンポーネントにアクセスするAPIのMock化ができるようになります。また、アサーションも充実しているためJest単体でも詳細なテストをかけるのではと感じました。
遅れた上に準備不足で大変稚拙な記事となってしまいましたが、本当はModule Mockなど書きたいことがありました。
準備が出来次第どんどん記事を更新していきたいと思います!

参考