在寫單元測試的時候有一個最重要的步驟就是Mock,咱們一般會根據接口來Mock接口的實現,好比你要測試某個class中的某個方法,而這個方法又依賴了外部的一些接口的實現,從單元測試的角度來講我只關心我測試的方法的內部邏輯,我並不關注與當前class自己依賴的實現,因此咱們一般會Mock掉依賴接口的返回,由於咱們的測試重點在於特定的方法,因此在Jest中一樣提供了Mock的功能,本節主要介紹Jest的Mock Function的功能。html
Mock 函數能夠輕鬆地測試代碼之間的鏈接——這經過擦除函數的實際實現,捕獲對函數的調用 ( 以及在這些調用中傳遞的參數) ,在使用 new
實例化時捕獲構造函數的實例,或容許測試時配置返回值的形式來實現。Jest中有兩種方式的Mock Function,一種是利用Jest提供的Mock Function建立,另一種是手動建立來覆寫自己的依賴實現。前端
假設咱們要測試函數 forEach
的內部實現,這個函數爲傳入的數組中的每一個元素調用一個回調函數,代碼以下:git
function forEach(items, callback) { for (let index = 0; index < items.length; index++) { callback(items[index]); } }
爲了測試此函數,咱們可使用一個 mock 函數,而後檢查 mock 函數的狀態來確保回調函數如期調用。github
const mockCallback = jest.fn(); forEach([0, 1], mockCallback); // 此模擬函數被調用了兩次 expect(mockCallback.mock.calls.length).toBe(2); // 第一次調用函數時的第一個參數是 0 expect(mockCallback.mock.calls[0][0]).toBe(0); // 第二次調用函數時的第一個參數是 1 expect(mockCallback.mock.calls[1][0]).toBe(1);
幾乎全部的Mock Function都帶有 .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);
在測試中,須要對函數如何被調用,或者實例化作斷言時,這些 mock 成員變量頗有幫助意義︰app
// 這個函數只調用一次 expect(someMockFunction.mock.calls.length).toBe(1); // 這個函數被第一次調用時的第一個 arg 是 'first arg' expect(someMockFunction.mock.calls[0][0]).toBe('first arg'); // 這個函數被第一次調用時的第二個 arg 是 'second arg' expect(someMockFunction.mock.calls[0][1]).toBe('second arg'); // 這個函數被實例化兩次 expect(someMockFunction.mock.instances.length).toBe(2); // 這個函數被第一次實例化返回的對象中,有一個 name 屬性,且被設置爲了 'test’ expect(someMockFunction.mock.instances[0].name).toEqual('test');
Mock 函數也能夠用於在測試期間將測試值注入您的代碼︰框架
const myMock = jest.fn(); console.log(myMock()); // > undefined myMock .mockReturnValueOnce(10) .mockReturnValueOnce('x') .mockReturnValue(true); console.log(myMock(), myMock(), myMock(), myMock());
用於函數連續傳遞風格(CPS)的代碼中時,Mock 函數也很是有效。 以這種風格編寫的代碼有助於避免那種須要經過複雜的中間值,來重建他們在真實組件的行爲,這有利於在它們被調用以前將值直接注入到測試中。異步
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] ]
大多數現實世界的例子實際上都涉及到將一個被依賴的組件上使用 mock 函數替代並進行配置,這在技術上(和上面的描述)是相同的。 在這些狀況下,儘可能避免在非真正想要進行測試的任何函數內實現邏輯。函數
有些狀況下超越指定返回值的功能是有用的,而且全面替換了模擬函數的實現。單元測試
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; }), };
你也能夠給你的Mock Function起一個準確的名字,這樣有助於你在測試錯誤的時候在輸出窗口定位到具體的Function
const myMockFn = jest .fn() .mockReturnValue('default') .mockImplementation(scalar => 42 + scalar) .mockName('add42');
最後,爲了更簡單地說明如何調用mock函數,咱們爲您添加了一些自定義匹配器函數:
// 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); // 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).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); // 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.mock.getMockName()).toBe('a mock name');
本文只是簡單的介紹了Mock Function的功能,更完整的匹配器列表,請查閱 參考文檔。
1. 前端測試框架Jest系列教程 -- Matchers(匹配器)
2.前端測試框架Jest系列教程 -- Asynchronous(測試異步代碼)