
本章内容:
- 单元测试
- Jest 和 Vue Test Utils 的基础和进阶全覆盖
- TDD,测试驱动开发,一种全新的开发方式
测试框架
安装:yarn add jest
查看版本:npx jext version
断言示例
测试结果
编辑器
如果使用的是 vscode 并且安装了 jest 插件,那么可以实时并且直观的看到测试是否通过
Jest实现异步测试
callback
// callbackconst fetchUser = (cb) => {setTimeout(() => {cb("hello")}, 100)}it('test callback', (done) => {fetchUser((data) => {expect(data).toBe("hello")done() //不加done,会忽略回调等待的时间})})
Promise
const userPromise = () => Promise.resolve("hello")it('test Promise', () => {//必须要returnreturn userPromise().then(data => {expect(data).toBe("hello")})})
aysnc await
// async awaitconst userPromise = () => Promise.resolve("hello")it('test async await', async () => {const data = await userPromise()expect(data).toBe("hello")})
expect
const userPromise = () => Promise.resolve("hello")const userPromiseReject = () => Promise.reject("error")// expect 必须returnit('test with expect', () => {//可以直接调用expect的resolves方法return expect(userPromise()).resolves.toBe("hello")})// expect rejectit('test with expect reject', () => {//可以直接调用expect的rejects方法return expect(userPromiseReject()).rejects.toBe("error")})
Jest 实现 Mock
为什么需要 Mock
- 前端需要网络请求
- 后端依赖数据库等模块
- 局限性:依赖其它的模块
Mock 解决方案
- 测试替代,将真实代码替换为替代代码。
Mock 的几大功能
- 创建 mock function,在测试中使用,用来测试回调
- 手动 mock,覆盖第三方实现
- 三大 API 实现不同粒度的时间控制
函数测试
```typescript 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<br />```typescriptfunction 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);})
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);})
第三方模块实现
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)})})

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

或者使用mockReturnValue结果一致axios.get.mockReturnValue(Promise.resolve({ data: { username: 'warbler' } }))
或者直接使用mockResolveValue结果一致axios.get.mockResolvedValue({ data: { username: 'warbler' } })
处理重复mock
问题:多次对同一个模块进行mock,产生重复的工作?
解决:在根目录新建mocks文件夹,新建需要mock的模块同名文件axios.js,jest会自动对这个文件夹下的文件进行处理。
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)})})
const axios = {get: jest.fn(() => Promise.resolve({ data: { username: 'warbler' } })),}module.exports = axios
mock Timer
- runAllTimers:执行完所有的 timer
- runOnlyPendingTimers:执行完正在等待的 timer
- advanceTimersByTime:精确控制时间流逝多少 ms ```typescript const fetchUser = (cb) => { setTimeout(() => { cb(‘hello’) }, 1000) }
// 所有的 timer 都被 jest 接管 jest.useFakeTimers() jest.spyOn(global, ‘setTimeout’) 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’)
})
```

