学习Jest——Mock Functions(模拟器·)

什么是mock函数

Mock函数可以轻松的测试代码之间的连接——这通过查处函数的实际实现,捕获对函数的调用(以及在这些调用中传递的参数),在使用new实例化的时候捕获构造函数的实例,或者允许测试的时候配置返回值的形式来实现。

Jest中创建Mock Function

  • 利用Jest提供的Mock Function创建,另外一种是手动创建来复写本身的依赖实现

  • 手动创建来覆写本身的依赖实现

接下来我们看一个例子:
假设我们要测试函数forEach的内部实现,这个函数为传入的数组中的每个元素调用一个回调函数,代码如下:

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

为了测试该函数,可以使用给一个mock函数,然后检查mock函数的状态来确保回调函数如期调用:

const mockCallback = jest.fn();
forEach([0, 1], mockCallback);

test('该模拟函数被调用了两次', () => {
    // 此模拟函数被调用了两次
    expect(mockCallback.mock.calls.length).toBe(2);
})

test('第一次调用函数时的第一个参数是0', () => {
    // 第一次调用函数时的第一个参数是 0
    expect(mockCallback.mock.calls[0][0]).toBe(0);
})

test('第二次调用函数时的第一次参数是1', () => {
    // 第二次调用函数时的第一个参数是 1
    expect(mockCallback.mock.calls[1][0]).toBe(1);
})

结果:


这样我们就使用一个Jest的Mock函数来测试forEach函数的内部调用情况了。

.mock属性

几乎所有的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成员变量很有帮助意义:

// 这个函数只调用一次
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的返回值

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);

输出的结果:


mock实现

大多数现实世界的例子实际上都涉及到将一个被依赖的组件上使用mock函数替代并进行配置,这在技术上(和上面的描述)是相同的。在这些情况下,尽量避免在非真正想要进行测试的任何函数内实现逻辑。

有些情况下指定返回值的功能是有用的,并且全面替换了模拟函数的实现

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

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

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

而当你需要创建复杂行为的模拟功能的时候,这样多个函数调用产生不同的结果的时候,请使用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名称

你也可以给你的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();
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容

  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,094评论 0 13
  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,685评论 0 38
  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,197评论 0 4
  • test
    shenciyou阅读 618评论 0 0
  • 今天 上午宿舍的两个人一起去图书馆了,我和另一个人一直睡到中午去吃饭。不是不想起床,真的是感冒难受的头疼昏昏沉沉,...
    追风少女李可爱_PUCK阅读 122评论 0 0