mock 函数
假设有个forEach函数,用来遍历一个数组,在遍历时候会调用callback, 我们想要知道callback的调用次数及调用参数是否符合预期,这时可以很方便的通过jest的函数模拟来实现。
function forEach(items, callback) {
for (let index = 0; index < items.length; index++) {
callback(items[index]);
}
}
为了测试这个forEach函数,我们可以使用mock函数模拟callback,并检查mock的状态,以确保按预期调用回调。
const mockCallback = jest.fn(x => 42 + x);
forEach([0, 1], mockCallback);
// mock函数被调用两次
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);
// 第一次调用的返回值是42
expect(mockCallback.mock.results[0].value).toBe(42);
通过 jest.fn(implementation?)可以用来模拟函数,其中函数实现可有可无,根据需要设定即可。
如果只是判断调用次数和调用参数,没有函数实现也不影响
test('mock 函数', () => {
// 仅仅创建一个空函数
const mockFun = jest.fn()
forEach([1,2],mockFun)
expect(mockFun.mock.calls.length).toBe(2)
expect(mockFun.mock.calls[0][0]).toBe(1)
});
.mock 属性
所以mock函数都有个特殊的.mock属性,记录函数如何被调用以及每次的返回值,也能追踪每次调用时的this。
const myMock1 = jest.fn();
const a = new myMock1();
console.log(myMock1.mock.instances);
// > [ <a> ]
const myMock2 = jest.fn();
const b = {};
const bound = myMock2.bind(b);
bound();
console.log(myMock2.mock.contexts);
// > [ <b> ]
.mock的各种属性如下
// 函数调用次数
expect(someMockFunction.mock.calls.length).toBe(1);
// 第一次调用的第一个参数
expect(someMockFunction.mock.calls[0][0]).toBe('first arg');
// 第一次调用的第二个参数
expect(someMockFunction.mock.calls[0][1]).toBe('second arg');
// 函数第一次调用的返回值
expect(someMockFunction.mock.results[0].value).toBe('return value');
// 函数第一次调用的上下文
expect(someMockFunction.mock.contexts[0]).toBe(element);
// 函数被实例化了两次
expect(someMockFunction.mock.instances.length).toBe(2);
// 第一次实例化对象的名称
expect(someMockFunction.mock.instances[0].name).toEqual('test');
// 函数最后一次调用的第一个参数
expect(someMockFunction.mock.lastCall[0]).toBe('test');
mock函数也可以通过函数匹配器来进行断言。
test('mock 函数', () => {
const mockFun = jest.fn(x => x*2)
forEach([1,2,3,3],mockFun)
//被调用次数
expect(mockFun).toBeCalledTimes(4)
//被调用的参数有1
expect(mockFun).toBeCalledWith(1)
expect(mockFun).not.toBeCalledWith(4)
//第4次调用参数为3
expect(mockFun).toHaveBeenNthCalledWith(4,3)
//最后一次调用参数为3
expect(mockFun).toHaveBeenLastCalledWith(3)
//有返回值
expect(mockFun).toHaveReturned()
//有返回值的有4次
expect(mockFun).toHaveReturnedTimes(4)
//返回值包含4
expect(mockFun).toHaveReturnedWith(4)
//最后一次返回值为6
expect(mockFun).toHaveLastReturnedWith(6)
//第3次返回值为6
expect(mockFun).toHaveNthReturnedWith(3, 6)
});
mock函数返回值
除了mock一个函数用来断言函数的调用信息之外,有时我们想模拟一个函数的输出值, 比如第一次调用返回10,第二次调用返回'x',后续调用都返回true
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
在类似filter这种场景,通过设定函数返回值可以非常方便地筛选想要的数据。
const filterTestFn = jest.fn();
// 第一次返回true,第二次返回false
filterTestFn.mockReturnValueOnce(true).mockReturnValueOnce(false);
const result = [11, 12].filter(num => filterTestFn(num));
console.log(result);
// > [11]
console.log(filterTestFn.mock.calls[0][0]); // 11
console.log(filterTestFn.mock.calls[1][0]); // 12
mock 模块(Modules)
假设我们有一个从API中获取用户信息的类。该类使用axios调用API,然后返回包含所有用户的数据属性。
user.js
import axios from 'axios';
class Users {
static all() {
return axios.get('/users.json').then(resp => resp.data);
}
}
export default Users;
现在,为了测试这种方法而不实际影响API(如创建缓慢而脆弱的测试), 我们可以使用jest.mock(…)函数自动模拟axios模块。
一旦我们模拟了模块, 我们就可以为.get提供mockResolvedValue以返回我们希望的数据。 也就是说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);
return Users.all().then(data => expect(data).toEqual(users));
});
模拟一部分
可以模拟模块的子集,模块的其余部分可以保留其实际实现:
foo-bar-baz.js
export const foo = 'foo';
export const bar = () => 'bar';
export default () => 'baz';
//test.js
import defaultExport, {bar, foo} from '../foo-bar-baz';
jest.mock('../foo-bar-baz', () => {
const originalModule = jest.requireActual('../foo-bar-baz');
//Mock default 和 foo,原来的bar还保持不变
return {
__esModule: true,
...originalModule,
default: jest.fn(() => 'mocked baz'),
foo: 'mocked foo',
};
});
test('should do a partial mock', () => {
const defaultExportResult = defaultExport();
expect(defaultExportResult).toBe('mocked baz');
expect(defaultExport).toHaveBeenCalled();
expect(foo).toBe('mocked foo');
expect(bar()).toBe('bar');
});
这种方式对于模拟一些复杂的数据很有用,比如要测试的方法依赖Vuex,我们需要模拟store中的部分数据, 通过这种方式可很方便实现。
jest.mock('@/store/index', () => {
return {
state: {
userInfo:{
}
}
}
})
假设我们要测试的文件中也引用了"@/store/index",那么数据将会变成我们模拟的数据,而不必真正依赖vuex。
我正在参加「创意开发 投稿大赛」详情请看:[掘金创意开发大赛来了!](https://juejin.cn/post/7120441631530549284 "https://juejin.cn/post/7120441631530549284")