首页
学习
活动
专区
圈层
工具
发布
50 篇文章
1
【架构师(第一篇)】整体需求分析和架构设计
2
【架构师(第二篇)】脚手架架构设计和框架搭建
3
【架构师(第三篇)】脚手架开发之掌握Lerna操作流程
4
【架构师(第四篇)】脚手架开发之Lerna源码分析
5
【架构师(第五篇)】脚手架之import-local执行流程及简历设计
6
【架构师(第六篇)】脚手架之需求分析和架构设计
7
【架构师(第七篇)】脚手架之准备阶段编写
8
【架构师(第八篇)】脚手架之 commander 框架使用方法
9
【架构师(第九篇)】如何让 Node 环境支持 ES Module
10
【架构师(第十篇)】脚手架之注册命令及架构优化
11
【架构师(第十一篇)】脚手架之命令注册和执行过程开发
12
【架构师(第十二篇)】脚手架之命令行交互工具 inquirer.js 使用方法
13
【架构师(第十三篇)】脚手架之创建项目准备阶段开发
14
【架构师(第十四篇)】脚手架之 egg.js 和 mongodb 的使用
15
【架构师(第十五篇)】脚手架之创建项目模板开发
16
【架构师(第十六篇)】脚手架之创建项目模板的下载与更新
17
【架构师(第十七篇)】脚手架之 ejs 和 glob 的使用
18
【架构师(第十八篇)】脚手架之项目模板的安装
19
【架构师(第十九篇)】脚手架之组件库模板开发
20
【架构师(第二十篇)】脚手架之自定义模板及第一阶段总结
21
【架构师(第二十一篇)】编辑器开发之需求分析和架构设计
22
【架构师(第二十二篇)】编辑器开发之项目整体搭建
23
【架构师(第二十三篇)】编辑器开发之画布区域组件的渲染
24
【架构师(第二十四篇)】编辑器开发之添加模版到画布
25
【架构师(第二十五篇)】编辑器开发之属性编辑区域表单渲染
26
【架构师(第二十六篇)】编辑器开发之属性编辑同步渲染
27
【架构师(第二十七篇)】前端单元测试框架 Jest 基础知识入门
28
【架构师(第二十八篇)】 测试工具 Vue-Test-Utils 基础语法
29
【架构师(第二十九篇)】Vue-Test-Utils 触发事件和异步请求
30
【架构师(第三十篇)】Vue-Test-Utils 全局组件和第三方库 vuex | vue-router
31
【架构师(第三十一篇)】前端测试之 TDD 的开发方式
32
【架构师(第三十二篇)】 通用上传组件开发及测试用例
33
【架构师(第三十三篇)】 Vue 中的实例及本地图片预览
34
【架构师(第三十四篇)】 业务组件库开发之 vue3 的插件系统
35
【架构师(第三十五篇)】 业务组件库开发之使用 Rollup 进行打包
36
【架构师(第三十六篇)】 业务组件库开发之发布到 NPM
37
【架构师(第三十七篇)】 服务端开发之后端框架与数据库技术选型
38
【架构师(第三十八篇)】 服务端开发之本地安装最新版 MySQL 数据库
39
【架构师(第三十九篇)】 服务端开发之连接 MySQL 数据库
40
【架构师(第四十篇)】 服务端开发之连接 Mongodb 数据库
41
【架构师(第四十一篇)】 服务端开发之安装并连接 Redis数据库
42
【架构师(第四十二篇)】 服务端开发之常用的登录鉴权方式
43
【架构师(第四十三篇)】 服务端开发之单元测试和接口测试
44
【架构师(第四十四篇)】 服务端开发之 pm2 和 nginx 介绍
45
【架构师(第四十五篇)】 服务端开发之认识 Github actions
46
【架构师(第四十六篇)】 服务端开发之安装 Docker
47
【架构师(第四十七篇)】 服务端开发之认识 Docker
48
【架构师(第四十八篇)】 服务端开发之 Dockerfile
49
【架构师(第四十九篇)】 服务端开发之认识 Docker-compose
50
【架构师(第五十篇)】 服务端开发之自动发布到测试机
清单首页架构文章详情

【架构师(第二十七篇)】前端单元测试框架 Jest 基础知识入门


单元测试

单元测试其实在我的实际开发中并没有用到过,但却经常听说,接下来进行单元测试的学习

  • JestVue Test Utils 的基础和进阶全覆盖
  • TDD,测试驱动开发,一种全新的开发方式

测试框架

  • 断言
  • Mock
  • 异步支持
  • 代码覆盖率

测试框架 jest 简介

特点

  • 开箱即用,零配置
  • 内置代码覆盖率
  • 容易 mock

安装

代码语言:javascript
复制
npm i --save-dev jest

查看版本

代码语言:javascript
复制
npx jest --version
27.5.1

断言示例

代码语言:javascript
复制
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 插件,那么可以实时并且直观的看到测试是否通过

Jest 实现异步测试

回调方式

代码语言:javascript
复制
// callback
const fetchUser = (cb) => {
  setTimeout(() => {
    cb("hello")
  }, 100)
}

it('test callback', (done) => {
  fetchUser((data) => {
    expect(data).toBe("hello")
    done()
  })
})

Promise

需要 return

代码语言:javascript
复制
// promise
const userPromise = () => Promise.resolve("hello")

it('test Promise', () => {
  return userPromise().then(data => {
    expect(data).toBe("hello")
  })
})

async await

代码语言:javascript
复制
// async await
const userPromise = () => Promise.resolve("hello")

it('test async await', async () => {
  const data = await userPromise()
  expect(data).toBe("hello")
})

expect

expect 会添加一些属性,也可以获取到 promiserejectresolve,需要 return

代码语言:javascript
复制
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")
})

Jest mock

为什么需要 Mock

  • 前端需要网络请求
  • 后端依赖数据库等模块
  • 局限性:依赖其它的模块

Mock 解决方案

  • 测试替代,将真实代码替换为替代代码。

Mock 的几大功能

  • 创建 mock function,在测试中使用,用来测试回调
  • 手动 mock,覆盖第三方实现,狸猫换太子
  • 三大 API 实现不同粒度的时间控制

函数测试

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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

第三方模块实现

代码语言:javascript
复制
// 一个真实的网络请求模块
const axios = require('axios')

module.exports = function getUserName(id) {
  return axios.get(`http://jsonplaceholder.typicode.com/users/${id}`).then((resp) => {
    return resp.data.username
  })
}

进行测试

代码语言:javascript
复制
const getUserName = require('./user')

it('test with mock modules', () => {
  return getUserName(1).then((name) => {
    console.log(name);
  })
})

结果输出了 Bret

接下来使用 jest 进行第三方模块 axiosmock

代码语言:javascript
复制
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 直接返回结果,结果是一样的。

代码语言:javascript
复制
axios.get.mockReturnValue(Promise.resolve({ data: { username: 'warbler' } }))

还用更简单的方式,直接返回一个 Promiseresolve

代码语言:javascript
复制
axios.get.mockResolvedValue({ data: { username: 'warbler' } })

如果多处对同一个模块进行 mock,会造成大量重复的工作,可以在根目录下新建 __mocks__ 文件夹, 然后新建需要 mock 的模块同名文件 axios.jsjest 会自动对这个文件夹下的文件进行处理。

代码语言:javascript
复制
const axios = {
  get: jest.fn(() => Promise.resolve({ data: { username: "warbler" } }))
}
module.exports = axios

timer mocks

  • runAllTimers:执行完所有的 timer
  • runOnlyPendingTimers:执行完正在等待的 timer
  • advanceTimersByTime:精确控制时间流逝多少 ms
代码语言:javascript
复制
const 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')
})
下一篇
举报
领券