はじめに
Axiosに続くモックネタ第2弾です。
業務で作成しているプログラムに、ランダムな値を発行するメソッドを持つクラスを呼び出す場面がありました。
その場面をJestにてテストするにあたり、「部分的に処理をモック化する」ことが難しかったので、やり方を忘れぬようメモとして、この記事を書きました。
環境情報
- TypeScript: 4.1.3
- Jest: 26.6.3
- Node.js: 12.18.3
テスト対象
実際のプロダクトコードと似たような状況のダミー処理を用意しました。
メインロジック
このmain
関数に対してテストを書いていきます。
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関数が呼び出しているユーティリティです。
KeyClass
のupdateKey
メソッドを呼び出すと、ランダムなキーが発行されます。
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
メソッドのみをモック処理に置き換えており、Time
やKeyClass.getKeyLength
は本来の処理のままとなっています。
注意
jest.mock
の呼び出しは、必ずdescribe
の外側で行ってください。
そうしないと、モック化が正しく行われません。
(私はそれに気づかず、小一時間ほど悩まされました・・・)
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);
});
});