本章内容:
- 单元测试
- Jest 和 Vue Test Utils 的基础和进阶全覆盖
- TDD,测试驱动开发,一种全新的开发方式
测试框架
安装:yarn add jest
查看版本:npx jext version
断言示例
测试结果
编辑器
如果使用的是 vscode 并且安装了 jest 插件,那么可以实时并且直观的看到测试是否通过
Jest实现异步测试
callback
// callback
const 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', () => {
//必须要return
return userPromise().then(data => {
expect(data).toBe("hello")
})
})
aysnc await
// async await
const 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 必须return
it('test with expect', () => {
//可以直接调用expect的resolves方法
return expect(userPromise()).resolves.toBe("hello")
})
// expect reject
it('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 />![](https://cdn.nlark.com/yuque/0/2022/webp/25963140/1656327006319-e95cdc58-5354-4b10-be6d-0bc7c34ad6f3.webp#clientId=ufcf1783f-3654-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=ufd331b88&margin=%5Bobject%20Object%5D&originHeight=940&originWidth=1721&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u33c12e5f-b266-4ef3-b5ea-df2fe58961a&title=)
```typescript
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);
})
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’) }) ```