メインコンテンツへスキップ
バージョン: 29.7

モック関数

非公式ベータ版翻訳

このページは PageTurner AI で翻訳されました(ベータ版)。プロジェクト公式の承認はありません。 エラーを見つけましたか? 問題を報告 →

モック関数を使用すると、実際の関数実装を消去し、関数呼び出し(および呼び出し時に渡されたパラメータ)をキャプチャし、newでインスタンス化されたコンストラクタ関数のインスタンスをキャプチャし、テスト時に戻り値を設定できるようになることで、コード間の連携をテストできます。

モック関数を作成する方法は2つあります:テストコードで使用するモック関数を作成する方法、またはモジュール依存関係をオーバーライドするmanual mockを作成する方法です。

モック関数の使用

配列の各要素に対してコールバックを実行するforEach関数の実装をテストする場面を想定してみましょう。

forEach.js
export function forEach(items, callback) {
for (const item of items) {
callback(item);
}
}

この関数をテストするには、モック関数を使用してモックの状態を検査し、コールバックが期待通りに呼び出されていることを確認できます。

forEach.test.js
const forEach = require('./forEach');

const mockCallback = jest.fn(x => 42 + x);

test('forEach mock function', () => {
forEach([0, 1], mockCallback);

// The mock function was called twice
expect(mockCallback.mock.calls).toHaveLength(2);

// The first argument of the first call to the function was 0
expect(mockCallback.mock.calls[0][0]).toBe(0);

// The first argument of the second call to the function was 1
expect(mockCallback.mock.calls[1][0]).toBe(1);

// The return value of the first call to the function was 42
expect(mockCallback.mock.results[0].value).toBe(42);
});

.mock プロパティ

すべてのモック関数にはこの特別な.mockプロパティがあり、関数がどのように呼び出されたか、関数が何を返したかに関するデータが保持されます。.mockプロパティは各呼び出しにおけるthisの値も追跡するため、これも検査可能です:

const myMock1 = jest.fn();
const a = new myMock1();
console.log(myMock1.mock.instances);
// > [ <a> ]

const myMock2 = jest.fn();
const b = {};
const bound = myMock2.bind(b);
bound();
console.log(myMock2.mock.contexts);
// > [ <b> ]

これらのモックメンバーは、関数がどのように呼び出され、インスタンス化され、何を返したかをテストでアサートするのに非常に有用です:

// The function was called exactly once
expect(someMockFunction.mock.calls).toHaveLength(1);

// The first arg of the first call to the function was 'first arg'
expect(someMockFunction.mock.calls[0][0]).toBe('first arg');

// The second arg of the first call to the function was 'second arg'
expect(someMockFunction.mock.calls[0][1]).toBe('second arg');

// The return value of the first call to the function was 'return value'
expect(someMockFunction.mock.results[0].value).toBe('return value');

// The function was called with a certain `this` context: the `element` object.
expect(someMockFunction.mock.contexts[0]).toBe(element);

// This function was instantiated exactly twice
expect(someMockFunction.mock.instances.length).toBe(2);

// The object returned by the first instantiation of this function
// had a `name` property whose value was set to 'test'
expect(someMockFunction.mock.instances[0].name).toBe('test');

// The first argument of the last call to the function was 'test'
expect(someMockFunction.mock.lastCall[0]).toBe('test');

モックの戻り値

モック関数は、テスト中にコードにテスト値を注入するためにも使用できます:

const myMock = jest.fn();
console.log(myMock());
// > undefined

myMock.mockReturnValueOnce(10).mockReturnValueOnce('x').mockReturnValue(true);

console.log(myMock(), myMock(), myMock(), myMock());
// > 10, 'x', true, true

モック関数は、関数型の継続渡しスタイルを使用するコードでも非常に効果的です。このスタイルで書かれたコードは、代用する実際のコンポーネントの動作を再現する複雑なスタブの必要性を回避し、使用直前にテストに値を直接注入することを可能にします。

const filterTestFn = jest.fn();

// Make the mock return `true` for the first call,
// and `false` for the second call
filterTestFn.mockReturnValueOnce(true).mockReturnValueOnce(false);

const result = [11, 12].filter(num => filterTestFn(num));

console.log(result);
// > [11]
console.log(filterTestFn.mock.calls[0][0]); // 11
console.log(filterTestFn.mock.calls[1][0]); // 12

実際の例の多くは、依存コンポーネントのモック関数を取得して設定することを伴いますが、手法は同じです。このような場合、直接テスト対象でない関数内にロジックを実装しようとする誘惑を避けてください。

モジュールのモック

APIからユーザーを取得するクラスがあるとします。このクラスはaxiosを使用してAPIを呼び出し、すべてのユーザーを含むdata属性を返します:

users.js
import axios from 'axios';

class Users {
static all() {
return axios.get('/users.json').then(resp => resp.data);
}
}

export default Users;

実際にAPIにアクセスせずに(つまり遅くて脆いテストを作成せずに)このメソッドをテストするには、jest.mock(...)関数を使用してaxiosモジュールを自動的にモック化できます。

モジュールをモック化したら、.getmockResolvedValueを提供して、テストが検証したいデータを返すようにできます。つまり、axios.get('/users.json')が偽のレスポンスを返すように設定しているのです。

users.test.js
import axios from 'axios';
import Users from './users';

jest.mock('axios');

test('should fetch users', () => {
const users = [{name: 'Bob'}];
const resp = {data: users};
axios.get.mockResolvedValue(resp);

// or you could use the following depending on your use case:
// axios.get.mockImplementation(() => Promise.resolve(resp))

return Users.all().then(data => expect(data).toEqual(users));
});

一部のモック

モジュールの一部をモック化し、残りの部分は実際の実装を保持できます:

foo-bar-baz.js
export const foo = 'foo';
export const bar = () => 'bar';
export default () => 'baz';
//test.js
import defaultExport, {bar, foo} from '../foo-bar-baz';

jest.mock('../foo-bar-baz', () => {
const originalModule = jest.requireActual('../foo-bar-baz');

//Mock the default export and named export 'foo'
return {
__esModule: true,
...originalModule,
default: jest.fn(() => 'mocked baz'),
foo: 'mocked foo',
};
});

test('should do a partial mock', () => {
const defaultExportResult = defaultExport();
expect(defaultExportResult).toBe('mocked baz');
expect(defaultExport).toHaveBeenCalled();

expect(foo).toBe('mocked foo');
expect(bar()).toBe('bar');
});

モックの実装

それでも、戻り値を指定する機能を超えて、モック関数の実装を完全に置き換えることが有用な場合があります。これはjest.fnまたはモック関数のmockImplementationOnceメソッドで行えます。

const myMockFn = jest.fn(cb => cb(null, true));

myMockFn((err, val) => console.log(val));
// > true

mockImplementationメソッドは、別のモジュールから作成されたモック関数のデフォルト実装を定義する必要がある場合に便利です:

foo.js
module.exports = function () {
// some implementation;
};
test.js
jest.mock('../foo'); // this happens automatically with automocking
const foo = require('../foo');

// foo is a mock function
foo.mockImplementation(() => 42);
foo();
// > 42

複数の関数呼び出しで異なる結果を生成するようなモック関数の複雑な動作を再現する必要がある場合は、mockImplementationOnceメソッドを使用します:

const myMockFn = jest
.fn()
.mockImplementationOnce(cb => cb(null, true))
.mockImplementationOnce(cb => cb(null, false));

myMockFn((err, val) => console.log(val));
// > true

myMockFn((err, val) => console.log(val));
// > false

mockImplementationOnceで定義された実装を使い切ると、jest.fnで設定されたデフォルト実装(定義されている場合)を実行します:

const myMockFn = jest
.fn(() => 'default')
.mockImplementationOnce(() => 'first call')
.mockImplementationOnce(() => 'second call');

console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn());
// > 'first call', 'second call', 'default', 'default'

通常チェーンされるメソッド(したがって常にthisを返す必要がある)の場合、すべてのモックに存在する.mockReturnThis()関数というシンプルなAPIが用意されています:

const myObj = {
myMethod: jest.fn().mockReturnThis(),
};

// is the same as

const otherObj = {
myMethod: jest.fn(function () {
return this;
}),
};

モックの名前

モック関数には任意で名前を付けることができます。この名前はテストのエラー出力で'jest.fn()'の代わりに表示されます。テスト出力でエラーを報告しているモック関数を迅速に特定したい場合は、.mockName()を使用してください。

const myMockFn = jest
.fn()
.mockReturnValue('default')
.mockImplementation(scalar => 42 + scalar)
.mockName('add42');

カスタムマッチャー

最後に、モック関数がどのように呼び出されたかを検証する手間を減らすため、いくつかのカスタムマッチャー関数を用意しました。

// The mock function was called at least once
expect(mockFunc).toHaveBeenCalled();

// The mock function was called at least once with the specified args
expect(mockFunc).toHaveBeenCalledWith(arg1, arg2);

// The last call to the mock function was called with the specified args
expect(mockFunc).toHaveBeenLastCalledWith(arg1, arg2);

// All calls and the name of the mock is written as a snapshot
expect(mockFunc).toMatchSnapshot();

これらのマッチャーは.mockプロパティを検査する一般的な方法を簡略化したものです。より好みに合う場合や特定の処理が必要な場合は、常に手動で検査することも可能です。

// The mock function was called at least once
expect(mockFunc.mock.calls.length).toBeGreaterThan(0);

// The mock function was called at least once with the specified args
expect(mockFunc.mock.calls).toContainEqual([arg1, arg2]);

// The last call to the mock function was called with the specified args
expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1]).toEqual([
arg1,
arg2,
]);

// The first arg of the last call to the mock function was `42`
// (note that there is no sugar helper for this specific assertion)
expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1][0]).toBe(42);

// A snapshot will check that a mock was invoked the same number of times,
// in the same order, with the same arguments. It will also assert on the name.
expect(mockFunc.mock.calls).toEqual([[arg1, arg2]]);
expect(mockFunc.getMockName()).toBe('a mock name');

マッチャーの完全なリストについては、リファレンスドキュメントを参照してください。