はじめに
TypeScriptで開発を行う際、テストツールとしてJestを利用しました。
その際、特定のテストに対しモックを設定したのですが、他のテスト結果にも影響してしまったので、Mockの初期化について整理し、備忘録としてまとめてみました。
Math.randomとDate.getMonthのモック化を例に紹介します。
目次
環境
node: 16.4.2
ts-node: 10.4.0
typescript: 4.5.5
@types/jest: 27.4.0
jest: 26.6.3
ts-jest: 26.5.6
Jestでのモック化と初期化方法
jest.spyOn()でのモック化
random関数が必ず0を返すモック
const spyRandom = jest.spyOn(global.Math, 'random').mockReturnValue(0);
上記以外の方法でもモック化は可能です。
その他、モック化の詳しい説明はこちらの記事にまとめています。
元の(モックされていない)実装に戻す
mockFn.mockRestore
spyRandom.mockRestore();
または、
(global.Math.random as jest.Mock).mockRestore();
mockFn.mockRestoreはjest.spyOnによって作成されたモックに対してのみ動作することに注意して下さい。
このため手動で jest.fn()を割り当てた場合は自分で復元作業を行わなければならないことに気をつけて下さい。
(JEST API Referenceより)
または、
restoreAllMocks
jest.restoreAllMocks();
全てのモックを初期値に戻します。
すべてのモック関数で.mockRestore()を呼び出すのと同じです。
(JEST API Referenceより)
または、
resetMocks
jestオプションのresetMocksをtrueにする
すべてのテストの前に、モックの状態と実装を自動的に復元します。
jest.restoreAllMocks()各テストの前に呼び出すのと同じです。
Jest の構成はプロジェクトの package.json または jest.config.js か jest.config.ts ファイル、または --config オプションを通して設定できます。
(JEST API Referenceより)
サンプルコード
export default class sampleMethod {
public getRandomNumber(): number {
return Math.random()
}
public getMonth(): number {
return new Date().getMonth()
}
public getHoge(): string{
return 'Hoge'
}
}
↓各テスト内で初期化する場合
import sampleMethod from './sampleMethod';
describe('テストを実行するたびに値が異なる関数', () => {
const SampleMethod = new sampleMethod;
test(`Math.random`, function () {
// Math.randomが必ず0を返すモック
const spyRandom = jest.spyOn(global.Math, 'random').mockReturnValue(0);
const resultNumber = SampleMethod.getRandomNumber();
// 初期化
spyRandom.mockRestore();
expect(resultNumber).toBe(0);
});
test(`Date.getMonth`, function () {
// 任意の日時を指定したモック
const mockDate = new Date('2022-01-01T01:00:00');
const spyDate = jest.spyOn(global, 'Date').mockImplementation(() => mockDate as unknown as string);
const resultMonth = SampleMethod.getMonth();
// 初期化
spyDate.mockRestore();
expect(resultMonth).toBe(0);
});
test(`Hoge`, function () {
const resultStr = SampleMethod.getHoge();
expect(resultStr).toBe('Hoge');
});
})
ハマったところ
タイポ
使い慣れてない言語とか関数とか使うと、やっぱりタイポを見落としがちです。
あとで、「ここかよ〜」ってならないために、まづはタイポのチェックを。
初期化位置
mockFn.mockRestore()
を書く位置によっては、そのテスト内でエラーがあった時に初期化のコードが実行されず、それ以降のテストに生き残ったモックが影響を与えてしまいます。
expect
の前にmockFn.mockRestore()
を書いた方が良さそうです。
const SampleMethod = new sampleMethod;
test(`Math.random`, function () {
// Math.randomが必ず0を返すモック
const spyRandom = jest.spyOn(global.Math, 'random').mockReturnValue(0);
const resultNumber = SampleMethod.getRandomNumber();
expect(resultNumber).toBe(0); // ←ここでエラーが出ると、この下に書いた初期化コードが実行されない
// 初期化 ↓↓ここに書いちゃった
spyRandom.mockRestore();
});
afterEach()での初期化
各テスト内でmockFn.mockRestore()
するのがめんどくさいので、afterEach()
で初期化をしようとしたところ、またハマりました。
afterEach(() => {
(global.Math.random as jest.Mock).mockRestore();
(global.Date as unknown as jest.Mock).mockRestore();
});
これで初期化しようとすると、モックを利用しないテストで怒られる
● テストを実行するたびに値が異なる関数 › Math.random
TypeError: global.Date.mockRestore is not a function
● テストを実行するたびに値が異なる関数 › Date.getMonth
TypeError: global.Math.random.mockRestore is not a function
● テストを実行するたびに値が異なる関数 › Hoge
TypeError: global.Math.random.mockRestore is not a function
ちなみに、jest.restoreAllMocks()
は問題ないです。
ただ、意図しないモックまで消えてしまわないかは不安です。
afterEach(() => {
jest.restoreAllMocks()
});
- 代替案
モック化した値を返してほしいのが、1回だけなら、.mockReturnValueOnce()
を使ってみるのも初期化忘れの対策になるかもしれません。
// Math.randomが1度だけ、必ず0を返すモック
const spyRandom = jest.spyOn(global.Math, 'random').mockReturnValueOnce(0);
最後に
モックの初期化忘れの対策として、より良い方法があれば、ぜひ教えてください。