单元测试其实在我的实际开发中并没有用到过,但却经常听说,接下来进行单元测试的学习
Jest 和 Vue Test Utils 的基础和进阶全覆盖TDD,测试驱动开发,一种全新的开发方式测试框架
Mock特点
mock安装
npm i --save-dev jest查看版本
npx jest --version
27.5.1断言示例
test('test common matcher', () => {
expect(2 + 2).toBe(4);
});
test('test not equal', () => {
expect(2 + 2).not.toBe(5);
});
test('test tp be true or false', () => {
expect(1).toBeTruthy();
expect(0).toBeFalsy();
});
test('test number', () => {
expect(4).toBeGreaterThan(3);
expect(2).toBeLessThan(3);
});
test('test object', () => {
expect({ name: 'warbler' }).toEqual({ name: 'warbler' });
});测试结果
编辑器
如果使用的是 vscode 并且安装了 jest 插件,那么可以实时并且直观的看到测试是否通过
// callback
const fetchUser = (cb) => {
setTimeout(() => {
cb("hello")
}, 100)
}
it('test callback', (done) => {
fetchUser((data) => {
expect(data).toBe("hello")
done()
})
})需要 return
// promise
const userPromise = () => Promise.resolve("hello")
it('test Promise', () => {
return userPromise().then(data => {
expect(data).toBe("hello")
})
})// async await
const userPromise = () => Promise.resolve("hello")
it('test async await', async () => {
const data = await userPromise()
expect(data).toBe("hello")
})expect 会添加一些属性,也可以获取到 promise 的 reject 和 resolve,需要 return。
const userPromise = () => Promise.resolve("hello")
const userPromiseReject = () => Promise.reject("error")
// expect
it('test with expect', () => {
return expect(userPromise()).resolves.toBe("hello")
})
// expect reject
it('test with expect reject', () => {
return expect(userPromiseReject()).rejects.toBe("error")
})为什么需要 Mock
Mock 解决方案
Mock 的几大功能
mock function,在测试中使用,用来测试回调mock,覆盖第三方实现,狸猫换太子API 实现不同粒度的时间控制function mockTest(shouldCall, cb) {
if (shouldCall) {
return cb(42)
}
}
it('test with mock function', () => {
// 创建一个假的函数实现
const mockCB = jest.fn()
mockTest(true, mockCB)
// 函数是否被调用过了
expect(mockCB).toHaveBeenCalled()
// 是否被参数调用
expect(mockCB).toHaveBeenCalledWith(42)
// 被调用的次数
expect(mockCB).toHaveBeenCalledTimes(1)
// 函数调用
console.log(mockCB.mock.calls);
// 函数调用结果
console.log(mockCB.mock.results);
})这里结果是 undefined ,因为并没有 mock 函数的实现,所以默认为 undefined。
function mockTest(shouldCall, cb) {
if (shouldCall) {
return cb(42)
}
}
it('test with mock implementation', () => {
const mockCB = jest.fn(x => x * 2)
mockTest(true, mockCB)
console.log(mockCB.mock.calls);
console.log(mockCB.mock.results);
})现在 mock 函数的实现, 返回参数的二倍,可以看见 value 变成了 84
function mockTest(shouldCall, cb) {
if (shouldCall) {
return cb(42)
}
}
it('test with mock mockReturnValue', () => {
const mockCB = jest.fn().mockReturnValue(20)
mockTest(true, mockCB)
console.log(mockCB.mock.calls);
console.log(mockCB.mock.results);
})还可以 mock 函数的返回值,可以看见 value 变成了 20
// 一个真实的网络请求模块
const axios = require('axios')
module.exports = function getUserName(id) {
return axios.get(`http://jsonplaceholder.typicode.com/users/${id}`).then((resp) => {
return resp.data.username
})
}进行测试
const getUserName = require('./user')
it('test with mock modules', () => {
return getUserName(1).then((name) => {
console.log(name);
})
})结果输出了 Bret
接下来使用 jest 进行第三方模块 axios 的 mock
const getUserName = require('./user')
// 先引入 axios 这个模块
const axios = require('axios')
// 调用 jest.mock 接管 axios 模块
jest.mock("axios")
// mock axios.get方法的实现
axios.get.mockImplementation(() => {
return Promise.resolve({ data: { username: 'warbler' } })
})
it('test with mock modules', () => {
return getUserName(1).then((name) => {
console.log(name);
expect(axios.get).toHaveBeenCalled()
expect(axios.get).toHaveBeenCalledTimes(1)
})
})结果已经变成了 warbler
或者使用 mockReturnValue 直接返回结果,结果是一样的。
axios.get.mockReturnValue(Promise.resolve({ data: { username: 'warbler' } }))还用更简单的方式,直接返回一个 Promise 的 resolve
axios.get.mockResolvedValue({ data: { username: 'warbler' } })如果多处对同一个模块进行 mock,会造成大量重复的工作,可以在根目录下新建 __mocks__ 文件夹, 然后新建需要 mock 的模块同名文件 axios.js,jest 会自动对这个文件夹下的文件进行处理。
const axios = {
get: jest.fn(() => Promise.resolve({ data: { username: "warbler" } }))
}
module.exports = axiostimertimermsconst fetchUser = (cb) => {
setTimeout(() => {
cb("hello")
}, 1000)
}
// 所有的 timer 都被 jest 接管
jest.useFakeTimers();
it('test the callback after 1 sec', () => {
const callback = jest.fn()
fetchUser(callback)
expect(callback).not.toHaveBeenCalled()
// setTimeout 此时是一个 mock function
expect(setTimeout).toHaveBeenCalledTimes(1)
// 一下子执行完所有的 timer
jest.runAllTimers()
// 是否被调用
expect(callback).toHaveBeenCalled()
// 调用的参数
expect(callback).toHaveBeenCalledWith('hello')
})
const loopFetchUser = (cb) => {
setTimeout(() => {
cb('one')
setTimeout(() => {
cb('two')
}, 2000)
}, 1000)
}
it('test the callback in loopFetchUser', () => {
const callback = jest.fn()
loopFetchUser(callback)
// 没有被调用
expect(callback).not.toHaveBeenCalled()
// 执行完正在等待的 timer
jest.runOnlyPendingTimers()
// 调用次数
expect(callback).toHaveBeenCalledTimes(1)
// 上一次调用的参数
expect(callback).toHaveBeenLastCalledWith('one')
// 执行完正在等待的 timer
jest.runOnlyPendingTimers()
// 调用次数
expect(callback).toHaveBeenCalledTimes(2)
// 上一次调用的参数
expect(callback).toHaveBeenLastCalledWith('two')
})
it('test the callback in advance timer', () => {
const callback = jest.fn()
loopFetchUser(callback)
// 没有被调用
expect(callback).not.toHaveBeenCalled()
// 控制时间流逝多少ms
jest.advanceTimersByTime(500)
// 控制时间流逝多少ms
jest.advanceTimersByTime(500)
// 调用次数
expect(callback).toHaveBeenCalledTimes(1)
// 控制时间流逝多少ms
expect(callback).toHaveBeenLastCalledWith('one')
jest.advanceTimersByTime(2000)
// 调用次数
expect(callback).toHaveBeenCalledTimes(2)
// 上一次调用的参数
expect(callback).toHaveBeenLastCalledWith('two')
})