Jestを使ったテスト手法まとめ
はじめに
Jestを使ったテストに触れていく中で公式サイトも参照したり記事を見て理解したりとしていましたが
自分で記事にすることで理解が深まったり公式サイトを閲覧しにいったりするため、
記憶として定着する面もあるのかなと思いましたので自身の備忘録としてまとめたいと思います。
認識ミスありましたら教えていただけますと幸いです。
jest.fn()
jest.fnは新しいモック関数を作成するために使用されます。モック関数はテスト中に呼び出された回数や引数を記録するほか、任意の実装を提供することもできます。
const mockFn = jest.fn();//jestの関数として定義
mockFn("arg1"); // モック関数を呼び出す、適当に引数を指定
expect(mockFn).toHaveBeenCalledTimes(1); //モック関数が呼ばれたことを確認
expect(mockFn).toHaveBeenCalledWith("arg1"); //モック関数の引数が"arg1"であることを確認
また、 jest.fn() に直接関数を渡すと、その関数がモックの実装になります。
const mockFn = jest.fn();
mockFn.mockImplementation((x: number) => x *2); //.mockImplementation(...) でその関数の動作(=中身)を指定
test('入力値を2倍になっているか', () => {
expect(mockFn(3)).tobe(6);
jest.mock()
モジュール全体をMockに置換。
jest.mockは、モジュール全体をまるごとモックに置き換える ために使用されます。
モジュール全体をモックに置き換えるので、そのモジュールが提供するすべての関数やオブジェクトを制御下に置くことができます。
import axios from 'axios';
export const fetchUser = async (id: number) => {
const response = await axios.get(`/api/users/${id}`);
return response.data;
};
import axios from 'axios';
import { fetchUser } from './api';
// axios をモック化する(本物の通信はしない)
jest.mock('axios');
// axios.get の型補完を有効にする
const mockedAxios = axios as jest.Mocked<typeof axios>;
describe('fetchUser', () => {
it('should return mocked user data', async () => {
// モックの戻り値を定義
mockedAxios.get.mockResolvedValue({
data: { id: 1, name: 'Taro' }
});
const user = await fetchUser(1);
expect(mockedAxios.get).toHaveBeenCalledWith('/api/users/1');
expect(user).toEqual({ id: 1, name: 'Taro' });
});
});
jest.spyOn()
実在するオブジェクトのメソッドをSpy/モックする。
特定のオブジェクトのメソッドをモックに置き換えて監視します。
アプリケーションに実在する関数の動きをチェックしたい時に使えます。
const spy = jest.spyOn(Math, 'random').mockImplementation(() => 0.1);
spy.mockRestore();
以下は公式のsampleコードです。
export const video = {
play() {
return '再生中';
},
};
import { video } from './video';
afterEach(() => {
// spyOnで監視したものを元に戻す(副作用防止)
jest.restoreAllMocks();
});
test('playが呼び出されたことを監視', () => {
const spy = jest.spyOn(video, 'play');
const result = video.play(); //spyしたメソッドを呼び出す
expect(spy).toHaveBeenCalled(); //spyしたメソッドを呼ばれたことを確認
expect(result).toBe('再生中'); //spyしたメソッドの戻り値であることを確認
});
また、監視しているメソッドの戻り値を上書きすることもできます。
test('playメソッドが"MOCKED"を常に返すことを確認'), () => {
const spy = jest.spyOn(video, 'play').mockImplementation(() => {
'MOCKED')
expect(video.play()).toBe('MOCKED');
expect(spy).toHaveBeenCalledTimes(1);
また、restoreAllMocksもしくはmockRestore メソッドを使うと、オリジナル関数に戻すことができます。
const obj = {
greet: () => 'こんにちは',
};
const spy = jest.spyOn(obj, 'greet').mockImplementation(() => 'Hello');
expect(obj.greet()).toBe('Hello'); // 戻り値を'Hello'に上書き
spy.mockRestore(); // spyOn() した関数を元に戻す
expect(obj.greet()).toBe('こんにちは'); // 戻り値として'こんにちは'が帰ってくる
jest.restoreAllMocks, jest.resetAllMocks(), jest.restoreAllMocks()の違い
メソッド | 対象 | 呼び出し履歴のクリア | 実装のリセット | 元の実装に戻す |
---|---|---|---|---|
jest.clearAllMocks() |
全てのモック関数 | ✅ | ❌ | ❌ |
jest.resetAllMocks() |
全てのモック関数 | ✅ | ✅(モック実装に戻す) | ❌ |
jest.restoreAllMocks() |
jest.spyOn() による関数 + jest.replaceProperty() によるプロパティ | ✅ | ✅ | ✅(オリジナルに戻す) |
- jest.resetAllMocks()の場合:すべてのモック関数 の 呼び出し履歴と実装(mockImplementation)を初期化する(mockReset() の全体版)
※ ただし spy 自体は維持される(オリジナルには戻らない) - jest.restoreAllMocks()の場合: 全てのjest.spyOn() でモック化した 関数、
jest.replaceProperty() で上書きした プロパティにのみ作用する
呼び出し履歴と実装をクリアし、元のオリジナル実装に戻す(mockRestore() の全体版) - jest.clearAllMocks()の場合: すべてのモック関数 の 呼び出し履歴のみ を消去する
💡 jest.clearAllMocks() や jest.resetAllMocks() は、jest.fn() で作られたモック関数にも jest.spyOn() で作られたモック関数にも作用します。
一方で、jest.restoreAllMocks() は、jest.spyOn() によってモック化された関数と、jest.replaceProperty() によって置き換えられたプロパティにのみ作用します。
なぜなら、jest.fn() で作られた関数には「元に戻す対象(オリジナルの関数)」が存在しないため、.mockRestore() のような**「restore(復元)」の概念がない**からです。
const obj = {
greet: () => 'こんにちは',
};
describe('resetAllMocksの挙動の確認' () => {
it('resetAllMocks は spy 実装を初期化するがオリジナルには戻さない', () => {
const spy = jest.spyOn(obj, 'greet').mockImplementation(() => 'Hello');
expect(obj.greet()).toBe('Hello'); // モック実装が呼ばれる
jest.resetAllMocks(); // 実装リセット、でも spy 自体は有効
expect(obj.greet()).toBe(undefined); // オリジナルには戻らず undefined
});
it('restoreAllMocks は spy をオリジナルに戻す', () => {
const spy = jest.spyOn(obj, 'greet').mockImplementation(() => 'Hello');
expect(obj.greet()).toBe('Hello');
jest.restoreAllMocks(); // spyが無効化されオリジナルに戻る
expect(obj.greet()).toBe('こんにちは');
});
it('restoreAllMocks は replacePropertyをオリジナルに戻す', () => {
// process.env をモック
jest.replaceProperty(process, 'env', { HOSTNAME: 'localhost' });
// モックが適用されているか確認
expect(process.env.HOSTNAME).toBe('localhost');
// restoreAllMocks によって元に戻る
jest.restoreAllMocks();
// 戻った後は本来の process.env に(localhost でなくなる)
expect(process.env.HOSTNAME).not.toBe('localhost');
});
});
const obj = {
greet: () => 'こんにちは',
};
describe('clearAllMocks は履歴のみリセットする', () => {
it('spy の呼び出し履歴のみをリセットするが、実装は残る', () => {
const spy = jest.spyOn(obj, 'greet').mockImplementation(() => 'Hello');
obj.greet(); // モックとして呼び出し
expect(spy).toHaveBeenCalledTimes(1);
jest.clearAllMocks(); // 呼び出し履歴のみリセット
expect(spy).not.toHaveBeenCalled(); // 呼び出し履歴が消えている
expect(obj.greet()).toBe('Hello'); // 実装は残っている
});
});
mockClear(), mockReset(), mockRestore()との違い
メソッド名 | 主な用途 | 呼び出し履歴のクリア | 実装のクリア | オリジナル実装に復元 | 使用対象 | 備考 |
---|---|---|---|---|---|---|
mockFn.mockClear() |
テストの使用状況(履歴)を初期化 | ✅ | ❌ | ❌ |
jest.fn() / spyOn()
|
.mock.calls / .mock.results / .mock.contexts などを全て初期化(使用履歴) |
mockFn.mockReset() |
使用履歴+モック実装を初期化 | ✅ | ✅ | ❌ |
jest.fn() / spyOn()
|
.mockClear() に加え、mock 実装を「空の関数(undefined を返す)」に置き換える |
mockFn.mockRestore() |
オリジナル関数に戻す(spy 用) | ✅ | ✅ | ✅ |
jest.spyOn() のみ |
.mockReset() 相当の動作+元の実装に完全復元。jest.fn() には使用できない |
mockClear()の例
jest.fn()の場合
const mockFn = jest.fn().mockReturnValue('Hello');
mockFn('a'); // ← 呼び出し履歴が1回追加される
mockFn.mockClear(); // ← 呼び出し履歴(calls, results など)を削除
expect(mockFn).not.toHaveBeenCalled(); //呼び出し履歴削除
expect(mockFn()).toBe('Hello'); // 実装は残る
jest.spyOnの場合
const spy = jest.spyOn(Math, 'random').mockImplementation(() => 100);
spy();
expect(spy).toHaveBeenCalledTimes(1);//1回呼ばれる
spy.mockClear(); // 呼び出し履歴だけクリア
expect(spy).not.toHaveBeenCalled();
expect(Math.random()).toBe(100); // 実装は残る
mockReset()の例
jest.fn()の場合
const mockFn = jest.fn().mockReturnValue('Hello');
mockFn();
mockFn.mockReset();
expect(mockFn()).toBe(undefined); // 実装も消える
expect(mockFn).not.toHaveBeenCalled(); //呼び出し履歴削除
jest.spyOnの場合
const spy = jest.spyOn(Math, 'random').mockImplementation(() => 100);
spy();
expect(spy).toHaveBeenCalledTimes(1); //1回呼ばれる
spy.mockReset(); // 呼び出し履歴 + 実装もクリア
expect(spy).not.toHaveBeenCalled();
expect(Math.random()).toBe(undefined); // 戻り値が undefined に
mockRestore()の例
jest.fn()の場合
const obj = {
greet: () => 'こんにちは',
};
const spy = jest.spyOn(obj, 'greet').mockImplementation(() => 'Hello');//上書き
expect(obj.greet()).toBe('Hello');
spy.mockRestore();
expect(obj.greet()).toBe('こんにちは'); // オリジナルに戻る
jest.spyOnの場合
const spy = jest.spyOn(Math, 'random').mockImplementation(() => 100);
spy();
expect(spy).toHaveBeenCalledTimes(1);
spy.mockRestore(); // spy 自体を解除してオリジナル実装へ戻す
expect(Math.random()).toBeLessThan(1); // 本来の Math.randomが返ってくる
まとめてみる
メソッド名 | 履歴のクリア (calls, results) |
実装のリセット (mockImplementation) |
オリジナル実装への復元 | 対象 | 備考 |
---|---|---|---|---|---|
mockFn.mockClear() |
✅ 呼び出し履歴を削除 | ❌ 実装は維持 | ❌ 復元されない |
jest.fn() / spyOn()
|
.mock.calls , .mock.results などをクリア(mock自体は変わらず) |
mockFn.mockReset() |
✅ | ✅ モック実装は undefined に戻る |
❌ |
jest.fn() / spyOn()
|
.mockClear() + 実装も消す。戻り値が未定義の空関数に置き換えられる |
mockFn.mockRestore() |
✅ | ✅ | ✅ オリジナル実装に戻す |
jest.spyOn() のみ |
spy した関数のみ。jest.fn() に対しては動作しない |
jest.clearAllMocks() |
✅ | ❌ | ❌ | 全てのモック関数 |
.mockClear() 相当の動作をグローバルに行う |
jest.resetAllMocks() |
✅ | ✅ | ❌ | 全てのモック関数 |
.mockReset() 相当をすべてに適用 |
jest.restoreAllMocks() |
✅ | ✅ | ✅ |
spyOn() / replaceProperty() された関数・プロパティのみ |
.mockRestore() 相当+ .replaceProperty() で置き換えたプロパティも復元 |
Jest Mock関数(事前の状態)
メソッド名 | 説明 | 例 |
---|---|---|
mockReturnValueOnce() |
1回限りの戻り値を設定 | mockFn.mockReturnValueOnce('A') |
mockResolvedValueOnce() |
1回限りのPromise が resolve する戻り値を設定 | mockFn.mockResolvedValueOnce('data') |
mockRejectedValueOnce() |
1回限りのPromise が reject する値(例外)を設定 | mockFn.mockRejectedValueOnce(new Error('err')) |
mockImplementation() |
モック関数の処理を関数で定義 | mockFn.mockImplementation((x) => x * 2) |
mockReturnValue() |
毎回同じ戻り値を返す | mockFn.mockReturnValue('fixed') |
mockResolvedValue() |
Promise で常に同じ値を resolve | mockFn.mockResolvedValue('OK') |
mockRejectedValue() |
Promise で常に同じ値を reject | mockFn.mockRejectedValue(new Error('fail')) |
mockReturnValueOnce
const mockFn = jest.fn();
mockFn.mockReturnValueOnce('first').mockReturnValueOnce('second');
test('mockReturnValueOnceの確認テスト', () => {
expect(mockFn()).toBe('first'); //1回限りの戻り値が返ってくる
expect(mockFn()).toBe('second'); //1回限りの戻り値が返ってくる
});
mockResolvedValueOnce
const mockFn = jest.fn();
mockFn.mockResolvedValueOnce('async value');
test('mockResolvedValueOnceの確認テスト', async () => {
await expect(mockFn()).resolves.toBe('async value');//1回限りの戻り値が返ってくる
});
mockRejectedValueOnce
const mockFn = jest.fn();
mockFn.mockRejectedValueOnce(new Error('failed'));
test('mockRejectedValueOnceの確認テスト', async () => {
await expect(mockFn()).rejects.toThrow('failed');
});
Jestマッチャ(出力の検証)
マッチャ名 | 比較方法 | プリミティブ型の挙動 | オブジェクト型の挙動 | 参照の違いに許容? | 説明 | 例(オブジェクト) |
---|---|---|---|---|---|---|
toBe() |
=== (厳密等価) |
✅ 値が同じならOK | ❌ 参照が違うとNG | ❌ 許容しない |
=== を使って比較。プリミティブは値、オブジェクトは参照で評価される |
expect({ a: 1 }).toBe({ a: 1 }) // ❌ NG const obj = {a: 1}; expect(obj).toBe(obj) // ✅ OK
|
toEqual() |
値の等価(浅い比較) | ✅ 値が同じならOK | ✅ 中身(プロパティの値と構造)が同じならOK | ✅ 許容する | オブジェクトの構造や値を再帰的に比較。参照が違っても中身が等しければOK | expect({ a: 1 }).toEqual({ a: 1 }) // ✅ OK |
toStrictEqual() |
厳密な構造と値の等価比較 | ✅ 値が同じならOK | ✅ プロパティの存在、順序、undefined の有無まで一致が必要 |
✅ 許容する |
toEqual() よりもさらに厳密な比較。余分な undefined プロパティがあるとNG |
expect({ a: 1 }).toStrictEqual({ a: 1, b: undefined }) // ❌ NG |
Jestマッチャ(モック関数の呼び出し検証)
マッチャ名 | 説明 | 例 |
---|---|---|
toHaveBeenCalled() |
呼び出されたかどうか | expect(mockFn).toHaveBeenCalled() |
toHaveBeenCalledTimes(n) |
n 回呼ばれたかどうか | expect(mockFn).toHaveBeenCalledTimes(2) |
toHaveBeenCalledWith(...) |
指定した引数で呼ばれたかどうか | expect(mockFn).toHaveBeenCalledWith('a', 1) |
toHaveBeenNthCalledWith(n, ...) |
n 回目の呼び出しが指定引数だったか確認 | expect(mockFn).toHaveBeenNthCalledWith(1, 'first') |
toHaveReturned() |
戻り値が存在したか | expect(mockFn).toHaveReturned() |
toHaveReturnedWith(value) |
特定の値を返したか | expect(mockFn).toHaveReturnedWith('value') |
シーン別使い分け
シーン | 推奨メソッド | やりたいこと | 理由(なぜそれを使うか) |
---|---|---|---|
テストの中でモック関数の呼び出し履歴だけをリセットしたい |
mockFn.mockClear() jest.clearAllMocks()
|
toHaveBeenCalled などで履歴だけ確認したいけど、実装は変えたくない |
実装を維持しつつ、アサーション前に履歴だけクリアしたいときに便利 |
毎回違う戻り値でテストしたい、完全にリセットしたい |
mockFn.mockReset() jest.resetAllMocks()
|
モックの履歴も実装(mockImplementationなど)も全部初期状態に戻したい |
mockReturnValueOnce などを何度も使い分けるテストで有効 |
jest.spyOn() で本物の関数をモック化したあと、元に戻したい |
mockFn.mockRestore() jest.restoreAllMocks()
|
モックの影響を他のテストに残したくない、本来の動作に戻したい | 本物の関数を一時的にモックして、終わったら確実に復元するために使う |
参考記事
まとめ
Jestによるテストの基本を以下の観点から整理しました。
- jest.fn() や jest.spyOn() を用いたモック関数の定義と動作
- mockReturnValueOnce() や mockResolvedValueOnce() を使った返り値の制御
- mockClear()、mockReset()、mockRestore() とその使い分け
- toBe()、toEqual()、toStrictEqual() などのマッチャの違いと注意点
- モック関数・マッチャを表にして整理
まだまだ知らないモック関数やJestのメソッドがありますが
使って少しずつ覚えていきたいと思います。
追記
わかりやすいリファレンスを見つけました
Discussion