Mock Functions
モック関数は機能の実際の実装を無くし、関数へのコール(そしてコールに渡されるパラメータ) をキャプチャし、 new
メソッドで初期化されたコンストラクタ関数のインスタンスをキャプチャすることでコード間の繋がりをテストすることを容易にし、テスト時の戻り値を設定することを可能にします。
関数をモックするには2つの方法があります: テストコードの中でモック関数を作成するか、 manual mock
を作成してモジュールの依存性を上書きするかです。
モック関数を利用する #
与えられた配列の要素のそれぞれにコールバックを実行する forEach
関数の実装をテストするケースを想像してみましょう。
function forEach(items, callback) { for (let index = 0; index < items.length; index++) { callback(items[index]); } }
この関数をテストするのにモック関数を利用して、コールバックが期待通り呼び出されることを確かめるためにモックの状態を見ることができます。
const mockCallback = jest.fn(); forEach([0, 1], mockCallback); // The mock function is called twice expect(mockCallback.mock.calls.length).toBe(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);
.mock
プロパティ #
全てのモック関数はその関数がどのように呼びだされたかを記録しておく、この特別な.mock
プロパティを持っています。 .mock
プロパティは個々のコールの this
の値も追跡するため、 this
も同様に検査できます:
const myMock = jest.fn(); const a = new myMock(); const b = {}; const bound = myMock.bind(b); bound(); console.log(myMock.mock.instances); // > [ <a>, <b> ]
これらのモック要素は関数が呼ばれたりインスタンス化されたかを確認するのにとても便利です:
// The function was called exactly once expect(someMockFunction.mock.calls.length).toBe(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'); // 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).toEqual('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(filterTestFn); console.log(result); // > [11] console.log(filterTestFn.mock.calls); // > [ [11], [12] ]
ほとんどの実世界の例では依存しているコンポーネントのモック関数を見つけ出して構成することが必要となりますが、技法そのものは一緒です。 これらのケースでは、あらゆる直接テストされていない関数の内側のロジックを実装したくなる誘惑を避けるように努めましょう。
モックの実装 #
とはいえ、指定された値を返すという能力を越えて完全に実装をモック化することが便利なケースがあります。 これはjest.fn
またはモック関数の mockImplementationOnce
メソッドを利用することで実現できます。
const myMockFn = jest.fn(cb => cb(null, true)); myMockFn((err, val) => console.log(val)); // > 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
を返す必要のある)メソッドがあるケースのために、この実装を単純化する糖衣APIを.mockReturnThis()
の形で全てのモックが備えています。
const myObj = { myMethod: jest.fn().mockReturnThis(), }; // is the same as const otherObj = { myMethod: jest.fn(function() { return this; }), };
カスタムマッチャ #
最後にモック関数がどのように呼ばれたかを検査するのをシンプルにするため、いくつかのカスタムマッチャを用意しておきました。
// The mock function was called at least once expect(mockFunc).toBeCalled(); // The mock function was called at least once with the specified args expect(mockFunc).toBeCalledWith(arg1, arg2); // The last call to the mock function was called with the specified args expect(mockFunc).lastCalledWith(arg1, arg2);
これらのマッチャは実際は .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).toContain([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 of an assertion) expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1][0]).toBe(42);
Matcher の一覧については、 reference docs を確認してください。