模拟函数
在Jest中提供了一个全局对象名为jest,这个对象上面有非常多的方法,有关该对象的方法可参考
jest对象上的方法大致可分为四类:
- 模拟模块。
- 模拟函数。
- 模拟计时器。
- 其它方法。
jest.fn
通过jest.fn方法可以创建并返回一个模拟函数。
jest.fn(implementation?)
implementation是一个可选参数,代表着模拟函数的实现,如果没有传入,那么创建的是一个空的模拟函数。
test('mock fn', () => {
const mock = jest.fn()
mock.mockReturnValue(42)
expect(mock()).toBe(42)
})
使用jest.fn方法创建了一个空的模拟函数,然后通过调用mockReturnValue方法来指定该模拟函数的返回值为42,之后通过expect调用对该模拟函数进行一个测试。
在使用jest.fn创建模拟函数的时候,也可以传入一个函数来代表模拟函数的实现,一般通过传入的函数能够明确所生成的模拟函数接收几个参数,返回值是多少:
test('mock fn', () => {
const mock = jest.fn((x) => x + 1)
expect(mock(1)).toBe(2)
})
更多的方法可查阅官方文档。
函数测试
这里实现了一个forEach函数,这个forEach就类似于数组里面的forEach方法,该函数会遍历数组里面的每一项,然后针对每一项执行对应的回调函数:
function forEach(arr, callback) {
for (let i = 0; i < arr.length; i++) {
callback(arr[i])
}
}
想要测试这个forEach函数的实现是否有问题,这里的forEach依赖了callback函数,因此可以通过模拟函数的方式来对其进行测试:
test('forEach', () => {
const mockFn = jest.fn((x) => x + 1)
forEach(arr, mockFn)
// [ [ 1 ], [ 2 ], [ 3 ] ]
// console.log(mockFn.mock.calls)
expect(mockFn.mock.calls).toHaveLength(3)
expect(mockFn.mock.calls.length).toBe(3)
// 测试每一次调用 callback 的时候传入的参数是否符合预期
expect(mockFn.mock.calls[0][0]).toBe(1)
expect(mockFn.mock.calls[1][0]).toBe(2)
expect(mockFn.mock.calls[2][0]).toBe(3)
// 针对每一次 callback 被调用后的返回值进行测试
expect(mockFn.mock.results[0].value).toBe(2)
// 模拟函数是否被调用过
expect(mockFn).toHaveBeenCalled()
// 前面在调用的时候是否有参数为 1 以及参数为 2 的调用
expect(mockFn).toHaveBeenCalledWith(1)
expect(mockFn).toHaveBeenCalledWith(2)
// 还可以对模拟函数的参数进行一个边界判断,判断最后一次调用是否传入的参数为 3
expect(mockFn).toHaveBeenLastCalledWith(3)
})
异步场景
模拟一个异步请求的场景。假设有如下的异步请求函数:
async function fetchData(){
const res = await fetch("https://www.example.com/data");
const data = await res.json();
return data;
}
在测试这个异步函数的时候,会发送真实的请求进行测试,但很多时候,我们知道这个请求没问题,或者说想要在那时屏蔽这一个异步,假设一个异步是能够正常返回数据的,这种情况下我们就可以针对这个异步请求函数来书写一个模拟函数来代替真实的fetchData函数:
const fetchMockDataFn = jest.fn()
const json = { id: 1, name: 'Tom' }
test('Mock async', async () => {
fetchMockDataFn.mockImplementation(() =>
Promise.resolve(json)
)
const data = await fetchMockDataFn()
expect(data).toEqual({ id: 1, name: 'Tom' })
})
test('Mock async error', async () => {
fetchMockDataFn.mockImplementationOnce(() =>
Promise.reject(new Error('network error'))
)
await expect(fetchMockDataFn()).rejects.toThrow(
'network error'
)
await expect(fetchMockDataFn()).resolves.toEqual({
id: 1,
name: 'Tom'
})
})