Jest - 模擬函數(shù) mock function

模擬函數(shù)通過擦去真正的函數(shù)實現(xiàn)析苫,捕獲函數(shù)調(diào)用(調(diào)用傳參)萌狂,當使用 new 實例化的時候捕獲構(gòu)造函數(shù)鹃栽,并允許測試時配置返回值躏率,從而更簡單地測試代碼之間的鏈接,民鼓。

這有兩種方法可以模擬函數(shù):要么創(chuàng)建一個模擬函數(shù)用于測試代碼薇芝,要么編寫一個手動模擬來覆蓋模塊依賴項。

使用一個 mock function

想象我們正在測試 forEach 函數(shù)的實現(xiàn)丰嘉,該函數(shù)為數(shù)組中每個項調(diào)用回調(diào)函數(shù)恩掷。

function forEach(items, callback) {
  for (let index = 0; index < items.length; index++) {
    callback(items[index]);
  }
}

為了測試這個函數(shù),我們使用一個 mock 函數(shù)供嚎,并且檢查這個 mock 的狀態(tài)以確保回調(diào)如期望所調(diào)用。

const mockCallback = jest.fn(x => 42 + x);
forEach([0, 1], mockCallback);

// 這個 mock 函數(shù)被調(diào)用了兩次
expect(mockCallback.mock.calls.length).toBe(2);

// 函數(shù)第一個調(diào)用的第一個參數(shù)是 0
expect(mockCallback.mock.calls[0][0]).toBe(0);

// 函數(shù)第二個調(diào)用的第一個參數(shù)是 1
expect(mockCallback.mock.calls[1][0]).toBe(1);

// 函數(shù)第一個調(diào)用的返回值是 42
expect(mockCallback.mock.results[0].value).toBe(42);
.mock 屬性

所有的 mock 函數(shù)都有這個特殊的 .mock 屬性克滴,這個屬性里面存儲了函數(shù)如何被調(diào)用和函數(shù)返回什么的數(shù)據(jù)逼争。這個.mock屬性也追蹤每個調(diào)用的this值,因此也可以檢查這個值:

const myMock = jest.fn();

const a = new myMock(); // 使用 new 方法新建實例 a
const b = {};
const bound = myMock.bind(b); // 將 myMock 的上下文綁定到 b
bound();

console.log(myMock.mock.instances);
// > [ <a>, <b> ]

這些 mock 成員在測試中非常有用劝赔,可以斷言這些函數(shù)如何被調(diào)用誓焦、實例化或返回什么:

// 這個函數(shù)實際上被調(diào)用了一次
expect(someMockFunction.mock.calls.length).toBe(1);

// 函數(shù)第一次調(diào)用的第一個參數(shù)是 'first arg'
expect(someMockFunction.mock.calls[0][0]).toBe('first arg');

// 函數(shù)第一次調(diào)用的第二個參數(shù)是 'second arg'
expect(someMockFunction.mock.calls[0][1]).toBe('second arg');

// 函數(shù)第一次調(diào)用的返回值是 ’return value‘
expect(someMockFunction.mock.results[0].value).toBe('return value');

// 這個函數(shù)被實例化了兩次
expect(someMockFunction.mock.instances.length).toBe(2);

// 函數(shù)第一個實例化返回的對象有一個 'name' 屬性值是 'test'
expect(someMockFunction.mock.instances[0].name).toEqual('test');
Mock 返回值

Mock 函數(shù)也可以用于在測試期間將測試值注入到代碼中:

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
// 優(yōu)先使用 mockReturnValueOnce 返回的值,當 mockReturnValueOnce 調(diào)用次數(shù)結(jié)束后着帽,默認返回 mockReturnValue 的值

對應一個持續(xù)傳遞的函數(shù)(forEach杂伟,filter)來說,在代碼里面使用 mock 函數(shù)是非常有效的仍翰。這樣就可以并不用去關注行為赫粥,而關注傳入的值是否正確。

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] ]

實際上予借,大多數(shù)實際示例都涉及到獲取依賴組件上的模擬函數(shù)并對其進行配置越平,但是技術是相同的。在這些情況下灵迫,盡量避免在沒有直接測試的函數(shù)中實現(xiàn)邏輯秦叛。

Mocking 模塊

假設我們有一個 class,從我們的 API 拉取用戶瀑粥。這個 class 使用 axios 去調(diào)用 API 挣跋,然后返回包含所有用的 data 屬性:

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

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

export default Users;

現(xiàn)在,為了在不實際碰到API的情況下測試這個方法(從而創(chuàng)建慢而脆弱的測試)狞换,我們可以使用jest.mock(…)函數(shù)來自動模擬axios模塊避咆。

模擬模塊之后,我們可以為.get提供mockResolvedValue哀澈,該值返回我們希望測試斷言的數(shù)據(jù)牌借。實際上,我們說的是希望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));
});
Mock 實現(xiàn)

不過膨报,有些情況超出了指定返回值的能力,這時候替換 mock 函數(shù)的實現(xiàn)則非常有用适荣。這個可以使用 jest.fn 或者 mockImplementationOnce 方法 mock 函數(shù)實現(xiàn)现柠。

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

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

當您需要定義從另一個模塊創(chuàng)建的模擬函數(shù)的默認實現(xiàn)時,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

當您需要重新創(chuàng)建模擬函數(shù)的復雜行為弛矛,以便多個函數(shù)調(diào)用產(chǎn)生不同的結(jié)果時够吩,請使用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

當模擬函數(shù)運行完用mockImplementationOnce定義的實現(xiàn)時,它將使用jest.fn()默認的實現(xiàn)集(如果定義了):

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 可以簡化 this周循,它的形式是.mockReturnThis()函數(shù)强法。

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

// is the same as

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

您可以選擇為模擬函數(shù)提供一個名稱,它將在測試錯誤輸出中顯示湾笛,而不是“jest.fn()”饮怯。如果希望能夠快速識別模擬函數(shù),并報告測試輸出中的錯誤嚎研,請使用此方法蓖墅。

const myMockFn = jest
  .fn()
  .mockReturnValue('default')
  .mockImplementation(scalar => 42 + scalar)
  .mockName('add42');
自定義匹配器

最后,為了更簡單地斷言如何調(diào)用模擬函數(shù)临扮,我們?yōu)槟砑恿艘恍┳远x匹配器函數(shù):

// 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).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 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.getMockName()).toBe('a mock name');

參考

mockFn.mock.calls
包含對這個模擬函數(shù)所做的所有調(diào)用的調(diào)用參數(shù)的數(shù)組贪壳。數(shù)組中的每個項都是在調(diào)用期間傳遞的參數(shù)數(shù)組。

例如:一個 mock 函數(shù) f 被調(diào)用了兩次靶橱,第一次使用參數(shù) f('arg1', 'arg2')寥袭,第二次使用參數(shù) f('arg3', 'arg4'),將有一個 mock.calls 數(shù)組如下所示:

[['arg1', 'arg2'], ['arg3', 'arg4']];

mockFn.mock.results
一個數(shù)組关霸,包含對這個模擬函數(shù)進行的所有調(diào)用的結(jié)果传黄。這個數(shù)組中的每個條目都是一個對象,其中包含一個type屬性和一個value屬性队寇。type的值如下:

  • 'return' - 指明這個調(diào)用完成后正常返回(return)膘掰。
  • 'throw' - 指明這個調(diào)用完成后拋出(throw)一個值。
  • 'incomplete' - 指明這個調(diào)用沒有完成佳遣。如果您從模擬函數(shù)本身或從模擬調(diào)用的函數(shù)中測試結(jié)果识埋,則會發(fā)生這種情況。

這個 value 屬性包含了一個拋出(throw)或返回(return)的值零渐。value 是 undefined 當 type === 'incomplete'窒舟。

例如:一個 mock 函數(shù) f 被調(diào)用了三次,返回 'result1'诵盼,然后拋出一個錯誤惠豺,最后返回 'result2',它的 mock.results 數(shù)組將如下所示:

[
  {
    type: 'return',
    value: 'result1',
  },
  {
    type: 'throw',
    value: {
      /* Error instance */
    },
  },
  {
    type: 'return',
    value: 'result2',
  },
];

mockFn.mock.instances
一個數(shù)組风宁,其中包含使用new從這個模擬函數(shù)實例化的所有對象實例洁墙。

例如:一個 mock 函數(shù)被實例化了兩次,mock.instances 數(shù)組如下:

const mockFn = jest.fn();

const a = new mockFn();
const b = new mockFn();

mockFn.mock.instance[0] === a; // true
mockFn.mock.instance[1] === b; // true

mockFn.mockReturnValue(value)
接受一個值戒财,該值將在調(diào)用模擬函數(shù)時返回热监。

const mock = jest.fn();
mock.mockReturnValue(42);
mock(); // 42
mock.mockReturnValue(43);
mock(); // 43

mockFn.mockReturnValueOnce(value)
接受一個值,該值將為對模擬函數(shù)的一次調(diào)用返回饮寞⌒⒖福可以被鏈接(chained)調(diào)用列吼,以便對模擬函數(shù)的連續(xù)調(diào)用返回不同的值。當不再使用mockReturnValueOnce值時疗琉,調(diào)用將返回mockReturnValue指定的值冈欢。

const myMockFn = jest
  .fn()
  .mockReturnValue('default')
  .mockReturnValueOnce('first call')
  .mockReturnValueOnce('second call');

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

mockFn.mockResolvedValue(value)
下面方法的語法糖函數(shù):

jest.fn().mockImplementation(() => Promise.resolve(value));

在異步測試 mock 異步函數(shù)特別有用:

test('async test', async () => {
  const asyncMock = jest.fn().mockResolvedValue(43);

  await asyncMock(); // 43
});

mockFn.mockResolvedValueOnce(value)
下面方法的語法糖:

jest.fn().mockImplementationOnce(() => Promise.resolve(value));

多次異步測試 resolve 不同的值非常有用:

test('async test', async () => {
  const asyncMock = jest
    .fn()
    .mockResolvedValue('default')
    .mockResolvedValueOnce('first call')
    .mockResolvedValueOnce('second call');

  await asyncMock(); // first call
  await asyncMock(); // second call
  await asyncMock(); // default
  await asyncMock(); // default
});
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市盈简,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌太示,老刑警劉巖柠贤,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異类缤,居然都是意外死亡臼勉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門餐弱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宴霸,“玉大人,你說我怎么就攤上這事膏蚓∑靶唬” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵驮瞧,是天一觀的道長氓扛。 經(jīng)常有香客問我,道長论笔,這世上最難降的妖魔是什么采郎? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮狂魔,結(jié)果婚禮上蒜埋,老公的妹妹穿的比我還像新娘。我一直安慰自己最楷,他們只是感情好整份,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著管嬉,像睡著了一般皂林。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蚯撩,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天础倍,我揣著相機與錄音,去河邊找鬼胎挎。 笑死沟启,一個胖子當著我的面吹牛忆家,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播德迹,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼芽卿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了胳搞?” 一聲冷哼從身側(cè)響起卸例,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎肌毅,沒想到半個月后筷转,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡悬而,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年呜舒,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片笨奠。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡袭蝗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出般婆,到底是詐尸還是另有隱情到腥,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布腺兴,位于F島的核電站左电,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏页响。R本人自食惡果不足惜篓足,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望闰蚕。 院中可真熱鬧栈拖,春花似錦、人聲如沸没陡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盼玄。三九已至贴彼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間埃儿,已是汗流浹背器仗。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人精钮。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓威鹿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親轨香。 傳聞我的和親對象是個殘疾皇子忽你,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容

  • ??引用類型的值(對象)是引用類型的一個實例科雳。 ??在 ECMAscript 中,引用類型是一種數(shù)據(jù)結(jié)構(gòu)脓杉,用于將數(shù)...
    霜天曉閱讀 1,066評論 0 1
  • HTML 5 HTML5概述 因特網(wǎng)上的信息是以網(wǎng)頁的形式展示給用戶的炸渡,因此網(wǎng)頁是網(wǎng)絡信息傳遞的載體。網(wǎng)頁文件是用...
    阿啊阿吖丁閱讀 3,906評論 0 0
  • 這是16年5月份編輯的一份比較雜亂適合自己觀看的學習記錄文檔丽已,今天18年5月份再次想寫文章,發(fā)現(xiàn)簡書還為我保存起的...
    Jenaral閱讀 2,767評論 2 9
  • 第5章 引用類型(返回首頁) 本章內(nèi)容 使用對象 創(chuàng)建并操作數(shù)組 理解基本的JavaScript類型 使用基本類型...
    大學一百閱讀 3,238評論 0 4
  • 第3章 基本概念 3.1 語法 3.2 關鍵字和保留字 3.3 變量 3.4 數(shù)據(jù)類型 5種簡單數(shù)據(jù)類型:Unde...
    RickCole閱讀 5,130評論 0 21