3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Jestでクラスのメソッドの一部だけを置き換えてテストする方法

Last updated at Posted at 2021-05-23

はじめに

Axiosに続くモックネタ第2弾です。
業務で作成しているプログラムに、ランダムな値を発行するメソッドを持つクラスを呼び出す場面がありました。
その場面をJestにてテストするにあたり、「部分的に処理をモック化する」ことが難しかったので、やり方を忘れぬようメモとして、この記事を書きました。

環境情報

  • TypeScript: 4.1.3
  • Jest: 26.6.3
  • Node.js: 12.18.3

テスト対象

実際のプロダクトコードと似たような状況のダミー処理を用意しました。

メインロジック

このmain関数に対してテストを書いていきます。

main-service.ts
import { KeyClass, Time } from './utils';

export function main(userID: string) {
  const key = new KeyClass({ userID });
  key.updateKey();

  return [...Time.today(), key.randomKey, key.getKeyLength()];
}

ユーティリティ

main関数が呼び出しているユーティリティです。
KeyClassupdateKeyメソッドを呼び出すと、ランダムなキーが発行されます。

utils.ts
import { randomBytes } from 'crypto';

export const Time = {
  today() {
    const date = new Date();
    return [date.getFullYear(), date.getMonth() + 1, date.getDate()];
  },
};

export class KeyClass {
  /** ユーザーID */
  userID: string;

  /** ランダムなキー */
  randomKey?: string;

  constructor(init: Partial<KeyClass>) {
    this.userID = init.userID || '';
    this.randomKey = init.randomKey;
  }

  updateKey(): void {
    this.randomKey = randomBytes(32).toString('hex');
  }

  getKeyLength(): number {
    return this.randomKey?.length ?? 0;
  }
}

テストコード

そのままだと結果にランダムな値が含まれてしまい、toEqualで戻り値をそのまま比較することが出来ません。(実際にはexpect.stringMatchingで正規表現を使った検証も可能ですが、ここでは触れません)
そこで、jest.mockを用いて一部分をモック化します。
以下の例では、KeyClass.randomKeyメソッドのみをモック処理に置き換えており、TimeKeyClass.getKeyLengthは本来の処理のままとなっています。

注意
jest.mockの呼び出しは、必ずdescribeの外側で行ってください。
そうしないと、モック化が正しく行われません。
(私はそれに気づかず、小一時間ほど悩まされました・・・)

__tests__/main-service.test.ts
import * as target from '../main-service';
import { KeyClass } from '../utils';

/** テスト用の戻り値 */
const RANDOM_KEY_FOR_TEST = '0123456789abcdef0123456789abcdef';

let keyClass: KeyClass; // コンストラクタ呼び出しを確認したいクラス
let mockUpdateKey: jest.Mock; // モック化したメソッド

jest.mock('../utils', () => {
  // 実際のモジュールを取得
  const Utils = jest.requireActual('../utils');
  return {
    // モック化不要なものはそのまま
    ...Utils,
    // テスト対象のみ置き換える
    KeyClass: jest.fn().mockImplementation((param) => {
      // 本物のインスタンスを生成
      keyClass = new Utils.KeyClass({ ...param });

      // モック化したいメソッドを置き換え
      // →結果はランダムな値ではなくなる
      mockUpdateKey = jest.fn().mockImplementation(() => {
        keyClass.randomKey = RANDOM_KEY_FOR_TEST;
      });
      keyClass.updateKey = mockUpdateKey;

      return keyClass;
    }),
  };
});

describe('main', () => {
  const USER_ID = 'test-user-id';

  beforeEach(() => {
    jest.clearAllMocks();
  });

  test('実行した年月日とランダムなキー、キー長が配列形式で返却されること', async () => {
    const today = new Date();
    const ymdArray = [today.getFullYear(), today.getMonth() + 1, today.getDate()];

    const expectedResult = [...ymdArray, RANDOM_KEY_FOR_TEST, 32];
    const result = target.main(USER_ID);
    expect(result).toEqual(expectedResult);
    expect(KeyClass).toHaveBeenCalledTimes(1);
    expect(KeyClass).toHaveBeenCalledWith({ userID: USER_ID });
    expect(mockUpdateKey).toHaveBeenCalledTimes(1);
  });
});

テストを実行した結果が、以下の出力です。
image.png

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?