前端測試框架Jest系列教程 -- Mock Functions(模擬器)

寫在前面:

  在寫單元測試的時候有一個最重要的步驟就是Mock,咱們一般會根據接口來Mock接口的實現,好比你要測試某個class中的某個方法,而這個方法又依賴了外部的一些接口的實現,從單元測試的角度來講我只關心我測試的方法的內部邏輯,我並不關注與當前class自己依賴的實現,因此咱們一般會Mock掉依賴接口的返回,由於咱們的測試重點在於特定的方法,因此在Jest中一樣提供了Mock的功能,本節主要介紹Jest的Mock Function的功能。html

Jest中的Mock Function

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(測試異步代碼)

   3.前端測試框架Jest系列教程 -- Mock Functions(模擬器)

   4.前端測試框架Jest系列教程 -- Global Functions(全局函數)

相關文章
相關標籤/搜索