什么是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();