はじめに
最近の TypeScript なら、関数は以前から存在する 'function' ではなくアロー関数を使うのが一般的だろう。しらんけど。
昨今はフロントエンドの隆盛もあってか、あるいは関数型言語からの影響か、そもそも js はクラスベースじゃないからなのか、わざわざ class にすることは少ないかもしれないが、私は日頃 c++ でなんでも class ベースで実装して暮らしているので class にまとめてしまう。そしてテストを書こうとする度に mock の使い方を忘れて調べ直すのだった。
もくじ
- 環境
- class の arrow function
- [class の static な arrow function] (#class-の-static-な-arrow-function)
- class の 非arrow function
- mock と元の振る舞い両方を利用したい場合
- 参考
環境
"jest": "^26.6.3"
"jest-cli": "^26.6.3"
"ts-jest": "^26.4.4"
"typescript": "^4.0.5"
class の arrow function
- インスタンス経由で呼び出すおそらくクラスで一番よく出てくる普通の関数
export class SoundPlayer {
public PlaySoundFile = () => {
...
}
}
結論
- jest.mock + テストで呼び出される arrow function に mock をセット
jest.mock('SoundPlayer');
test('SoundPlayerConsumer call SoundPlayer.PlaySoundFile', () => {
mocked(SoundPlayer).prototype.PlaySoundFile = jest.fn(() => {});
expect(mocked(SoundPlayer).prototype.PlaySoundFile).toBeCalled();
});
説明
jest.mock('path/to/SoundPlayer')
では arrow function は置き換わらないが、個別にモック関数を指定すれば
インスタンスメソッドを置き換える事ができる。
ちなみに単に jest.mock
した状態で arrow function が呼び出されると関数ではないと怒られる
class Dog {
public Bark = () => {
console.log('bow wow');
}
}
...
jest.mock('path/to/SoundPlayer');
test('it failed', () => {
const player = new SoundPalyer('sound.mp3');
player.PlaySoundFile(); // > TypeError: player.PlaySoundFile is not a function
})
クラスの prototype をモックに変えればインスタンスのメソッドが差し替わる事とイコールであることは理解できる。
jest.mock
により元の arrow function は消えてしまい、prototype の属性としてモックを指定していると考えればいいのだと思う
class の static な arrow function
class SoundPlayer {
public static Play = (file: string) => {
...
}
}
結論
- jest.mock で OK
jest.mock('SoundPlayer');
test('Mock Static Methode', () => {
SoundPlayer.Play('Last Christmas');
expect(mocked(SoundPlayer).Play).toBeCalled();
});
テストの後は jest.resetAllMocks
などでモックの状態をリセットすることを忘れないように
class の 非arrow function
- これは
jest.mock
で OK. すべての処理をモックにする必要がない場合や、単に spy 的な
機能を利用したい場合はjest.spyOn
を使うといいだろう (メソッドについては prototype を利用する)
const m = jest.spyOn(SoundPlayer.prototype, 'PlaySoundFile');
mock と元の振る舞い両方を利用したい場合
- import 文を使わず、import 関数を利用して都度 doMock, import を行えば可能
- 参考 https://zenn.dev/kojiroueda/articles/ad28c45bb44b2e
test('mocked Bark', async() => {
jest.resetModules();
jest.doMock('./dog'); // Dod クラスを mock 化
const { Dog } = await import('./dog'); // import
mocked(Dog).prototype.Bark = jest.fn(() => {
console.log('called mocked Bark!!');
}); // Bark の振る舞いをモックに差し替える
const { Breeder } = await import('./breeder'); // Breeder は Dog クラスを利用する
// Dog を mock 化した上で import することで、 Breeder は mock 化された Dog クラスを利用する
const b = new Breeder();
b.Show();
}
test('original Bark', async() => {
jest.resetModules();
jest.dontMock('./dog'); // mock を利用しない
const { Breeder } = await import('./breeder');
const b = new Breeder();
b.Show();
});
-
import()
の代わりにrequire()
も利用可能
参考
以下の情報を参考にしました🙇♂️