学习 Jest 时,首先阅读官方文档,大致浏览下 API ,然后再去看一些大牛的博客或教程,会让学习的效果最大化。同时可以自己在本地起一个服务,实践学习到的知识。
下面我们先来看如何搭建服务,方便实践代码。
搭建项目
初始化
# 创建项目
mkdir study-jest
cd study-jest
# 初始化
npm init -y
# 安装依赖
npm i -D jest@27.5.1
目前 Jest 已经来到 29 版本了,但是
Jest@28
和react@18
以及@testing-library/react
一起使用时会有冲突,建议使用 稳定 的版本。
安装 Jest 后,用 jest-cli
初始化 jest
配置文件:
npx jest --init
可以按照图中的配置走,如果要使用 ts 的话也可以,后面会讲解如何让
Jest
xTypeScript
“合作”。
修改 package.json
中的 scripts
为:
{
"scripts": {
"test": "jest --watchAll", // 类似热更新,这样就不用频繁执行命令了
"coverage": "jest --coverage" // 生成 web 页面文件
}
}
支持 ts
如果在项目中使用 TypeScript ,由于 Jest 本身不做代码转译工作 ,那么我们在编写测试时需要调用已有的 转译器/编译器 来做代码转译,常见的转译器有 Babel 以及 TSC。
这里默认读者已经在项目中使用了 TypeScript ,下面直接讲解转译的配置步骤:
- 安装
ts-jest
:
npm i -D ts-jest@27.1.4
注意,这里
ts-jest
一定要和jest
的大版本一致! 比如 27 对 27,或者 26 对 26,否则会有兼容问题!
- 在
jest.config.js
里添加一行配置:
module.exports = {
preset: 'ts-jest',
// ...
};
- 安装对应的 Jest 类型声明包:
npm i -D @types/jest@27.4.1
同样地,TS 声明类型包的大版本最好和
jest
一样。
- 在
tsconfig.json
里加上jest
和node
类型声明:
{
"compilerOptions": {
"types": ["node", "jest"]
}
}
- 最后执行
npm run test
,测试通过。
路径别名
在引入其他文件的变量或函数时,我们可能需要写很长一句路径,是不是可以优化一下呢?
要实现这样的效果,我们可以在 jest.config.js
中的 moduleDirectories
添加 "src"
:
// jest.config.js
module.exports = {
moduleDirectories: ["node_modules", "src"],
// ...
}
显而易见的,这是写给 Jest 看的,别忘了还有转译器 TSC ,在 tsconfig.json
里指定 baseUrl
和 paths
路径:
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"utils/*": ["src/utils/*"]
}
}
}
值得一提的是,所谓的 “路径简写” 本质上只是路径映射。所以
tsconfig.json
里的paths
就是把utils/xxx
映射成src/utils/xxx
, 而jest.config.js
里的moduleDirectories
则是把utils/sum
当作第三方模块,先在node_modules
里找,找不到再从src/xxx
下去找。 所以这两者是有区别的。
支持 import 和 ES6 语法
安装 babel 核心包:
npm i @babel/core@7.4.5 @babel/preset-env -D
在项目根目录新建 .babelrc
文件,通过这个配置文件告诉 babel 应该如何将 ES6 转换成 ES5 :
{
"presets": [
[
"@babel/preset-env", {
"targets": {
"node": "current
}
}
]
]
}
- 为什么安装了 babel 的核心包,然后写下配置文件,就可以达到转换语法的目的?
- 这是因为 Jest 中有
babel-jest
组件,当我们使用npm run test
时,它会先去检测根目录或者开发环境中是否安装了 babel 核心包,由于package.json
中安装了 babel ,紧接着就会去找.babelrc
文件,然后开始转化。
测试环境
很多时候,我们前端的代码需要在浏览器里运行,经常需要用到浏览器的 API ,例如 localStorage
、cookie
等,但是我们并不能直接这样测试,因为 Node.js 环境没有 localStorage
,所以会得到报错:localStorage is not defined
。
既然 Node.js 没有 localStorage
,那么我们想办法让运行环境支持使用浏览器的 API ,Jest 提供了这样的功能,即 testEnvironment
配置。
在 jest.config.js
中配置:
module.exports = {
testEnvironment: "json"
}
添加了 jsdom
测试环境之后,全局会自动拥有完整的浏览器标准 API 。原理 是 jsdom
这个库自己用 JS 实现了一套 Node.js 环境下的 Web 标准 API 。由于 Jest 的测试文件也是 Node.js 环境下执行的,所以 Jest 用这个库充当了浏览器环境的 Mock 实现。
我们可以写一段测试:
在 src/utils/storage.ts
写入 localStorage
的添加和删除:
const KEY_NAME = "my-app-";
const set = (key: string, value: string) => {
localStorage.setItem(KEY_NAME + key, value);
};
const get = (key: string) => {
return localStorage.getItem(KEY_NAME + key);
};
const storage = {
get,
set,
};
export default storage;
在 tests/utils/storage.test.ts
添加测试用例:
import storage from "utils/storage";
describe("storage", () => {
it("可以缓存值", () => {
storage.set("newKey", "hello");
expect(localStorage.getItem("my-app-newKey")).toEqual("hello");
});
it("可以设置值", () => {
localStorage.setItem("my-app-newKey", "hello");
expect(storage.get("newKey")).toEqual("hello");
});
});
执行 npm run test
可以看到测试是成功的
Jest 常见匹配器
toEqual
test('toEqual 匹配器', () => {
const a = { number: '1' }
expect(a).toEqual({ number: '1' })
}
toBeNull
test('toBeNull 匹配器', () => {
const a = null
expect(a).toBeNull()
}
toBeUndefined
test('toBeUndefined 匹配器', () => {
const a = toBeUndefined
expect(a).toBeUndefined()
}
toBeDefined
test('toBeDefined 匹配器', () => {
const a = 'foursheep' // 任意定义了变量,就能通过测试
expect(a).toBeDefined()
}
toBeTruthy
test('toBeTruthy 匹配器', () => {
const a = 1 // 这个值返回 true ,就能通过测试
expect(a).toBeTruthy()
}
toBeFalsy
test('toBeFalsy 匹配器', () => {
const a = 0 // 这个值返回 false ,就能通过测试
expect(a).toBeFalsy()
}
toBeFalsy
test('toBeFalsy 匹配器', () => {
const a = 0 // 这个值返回 false ,就能通过测试
expect(a).toBeFalsy()
}
toBeGreaterThan
类似于 >
test('toBeGreaterThan 匹配器', () => {
const count = 10
expect(count).toBeGreaterThan(9)
}
toBeLessThan
类似于 <
test('toBeLessThan 匹配器', () => {
const count = 10
expect(count).toBeLessThan(11)
}
toBeGreaterThanOrEqual
类似于 >=
test('toBeGreaterThanOrEqual 匹配器', () => {
const count = 10
expect(count).toBeGreaterThanOrEqual(10)
}
toBeLessThanOrEqual
类似于 <=
test('toBeLessThanOrEqual 匹配器', () => {
const count = 10
expect(count).toBeLessThanOrEqual(11)
}
toBeCloseTo
test('toBeCloseTo 匹配器', () => {
const one = 0.1
const two = 0.2
expect(one + two).toBeCloseTo(0.3)
}
toMatch
匹配字符串
test('toMatch 匹配器', () => {
const str = '1, 2, 3'
expect(str).toMatch(4)
}
toContain
匹配数组
test('toContain 匹配器', () => {
const arr = ['1', '2', '3']
const setArr = new set(arr)
expect(arr).toContain('4')
expect(setArr).toContain('4')
}
toThrow
抛出异常才可以通过测试
const throwNewErrorFunc = function () {
throw new Error ('this is Error')
}
test('toThrow 匹配器', () => {
expect(throwNewErrorFunc).not.toThrow()
}
Jest 中的钩子函数
四个钩子
beforeAll
在 所有 测试用例执行 之前 要执行一次这个钩子函数
beforeEach
在 每一个 测试用例执行 之前 都要执行这个钩子函数
afterAll
在 所有 测试用例执行 之后 要执行一次这个钩子函数
afterEach
在 每一个 测试用例执行 之后 都要执行这个钩子函数
钩子函数的作用域
- 钩子函数在父级分组可作用于子集,类似于继承
- 钩子函数同级分组作用域是互不干扰的,各其作用
- 先执行外部的钩子函数,再执行内部的钩子函数