Appearance
Jest
Jest是一个令人愉快的 JavaScript 测试框架,专注于简洁明快。他适用但不局限于使用以下技术的项目:Babel, TypeScript, Node, React, Angular, Vue
特性:
- 零配置
- 快照
- 提高性能
- 优秀的 api
- 代码覆盖率
- 轻松模拟Mock Functions
- 优秀的报错信息
安装使用
- 创建文件夹,初始化npm
npm init -y
- 安装jest
npm i --save-dev jest
(如果taobao源安装不好就用npm源) - 创建math.js
js
function sum (x, y) {
return x + y
}
function subtract (x, y) {
return x - y
}
exports.sum = sum
exports.subtract = subtract
- 创建math.test.js,把方才的代码拿过来,不过test和expect就是jest的API,而且test和expect不用引用,全局的变量,jest会注入相关的API,直接零配置就可以使用。
js
const { sum, subtract } = require('./math')
test('测试 sum', () => {
expect(sum(1, 2)).toBe(3)
})
test('测试 subtract', () => {
expect(subtract(2, 1)).toBe(1)
})
- 命令行
npx jest
运行测试通过
bash
PS E:\professer\Jest\3-5-jest> npx jest
PASS ./math.test.js (5.943 s)
√ 测试 sum (10 ms)
√ 测试 subtract (1 ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 27.459 s
- 如果把减法改成乘法运行,这里就会报错,提示是哪个地方出错。
bash
PS E:\professer\Jest\3-5-jest> npx jest
FAIL ./math.test.js
√ 测试 sum (3 ms)
× 测试 subtract (6 ms)
● 测试 subtract
expect(received).toBe(expected) // Object.is equality
Expected: 1
Received: 2
6 |
7 | test('测试 subtract', () => {
> 8 | expect(subtract(2, 1)).toBe(1)
| ^
9 | })
at Object.<anonymous> (math.test.js:8:26)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 passed, 2 total
Snapshots: 0 total
Time: 3.926 s, estimated 6 s
- 还可以修改package.json,修改script,直接用
npm run test
去进行测试,还可以添加一个监听参数--watchAll
,自动执行测试命令
js
"scripts": {
"test": "jest",
"test:watch": "jest --watchAll"
},
配置文件
项目零配置就可以使用,还可以通过配置文件修改配置
- 命令行执行
npx jest --init
生成配置文件
bash
# 是否用ts的语法创建配置文件
? Would you like to use Typescript for the configuration file? ... no
# 选择环境,jsdom可以兼容node
? Choose the test environment that will be used for testing » - Use arrow-keys. Return to submit.
node
> jsdom (browser-like)
# 你是否想添加测试报告
? Do you want Jest to add coverage reports? » (y/N) y
# 你想通过哪个provider去提供代码覆盖率
? Which provider should be used to instrument code for coverage? » - Use arrow-keys. Return to submit.
v8
> babel
# 是否在测试之前自动清除mock的数据
? Automatically clear mock calls and instances between every test? » (y/N) y
# 生成jest.config.js成功
📝 Configuration file created at E:\professer\Jest\3-5-jest\jest.config.js
- 生成的文档中有所有的配置项,在配置文件中也有所有的,只不过是注释着的。可以通过查询 官方配置 去学习。
Jest结合使用Babel
我们通常写项目用的是ES Module,并不是CommonJS的方式,这个时候需要通过Babel去加载功能
- 把math.js和math.test.js进行ES Module的转换
js
// math.js
export function sum (x, y) {
return x + y
}
export function subtract (x, y) {
return x - y
}
js
// math.test.js
import { sum, subtract } = './math'
test('测试 sum', () => {
expect(sum(1, 2)).toBe(3)
})
test('测试 subtract', () => {
expect(subtract(2, 1)).toBe(1)
})
这个时候直接运行会报错
- 这种情况就要使用babel和babel-jest,babel-jest就是babel的适配器,jest在运行之前会调用babel把ES Module转换成CommonJS模块再来运行测试。执行
npm i babel-jest @babel/core @babel/preset-env -D
- 根目录创建
babel.config.js
,并写
js
// babel.config.js
module.exports = {
presets: [['@babel/preset-env', {targets: {node: 'current'}}]],
};
- 这样就可以直接使用了
npm run test
运行流程
npm run test 运行的jest,jest内部使用了babel-jest,其内部又使用了@babel/core, 把 es6 模块转换为 commonjs 模块,并把转换之后的模块给 jest 使用,运行测试代码
使用其他模块
里面都有使用了webpack,parcel,typescript的配置方式,都可以进行兼容。
Jest中常用的匹配器
主要的API就是匹配器,toBe就是相等匹配器
简单匹配器
js
test('two plus two is four', () => {
expect(2 + 2).toBe(4);
});
toBe使用 Object.is 来测试精确相等,如果是检查对象类型就不好用
js
test('测试对象', () => {
const obj = { foo: 'bar' }
expect(obj).toBe({ foo: 'bar' })
})
bash
expect(received).toBe(expected) // Object.is equality
If it should pass with deep equality, replace "toBe" with "toStrictEqual"
Expected: {"foo": "bar"}
Received: serializes to the same string
11 | test('测试对象', () => {
12 | const obj = { foo: 'bar' }
> 13 | expect(obj).toBe({ foo: 'bar' })
| ^
14 | })
这里建议使用toEqual代替,toEqual 递归检查对象或数组的每个字段:
js
test('测试对象', () => {
const obj = { foo: 'bar' }
expect(obj).toEqual({ foo: 'bar' })
})
有效性匹配
js
test('null', () => {
const n = null;
expect(n).toBeNull();
expect(n).toBeDefined();
expect(n).not.toBeUndefined(); // 等同于toBeDefined
expect(n).not.toBeTruthy(); // 等同于toBeFalsy
expect(n).toBeFalsy();
});
数字匹配
js
test('数字匹配 —— two plus two', () => {
const value = 2 + 2;
expect(value).toBeGreaterThan(3); // 大于
expect(value).toBeGreaterThanOrEqual(3.5); // 大于等于
expect(value).toBeLessThan(5); // 小于
expect(value).toBeLessThanOrEqual(4.5); // 小于等于
// toBe和toEqual也可以测试数字
expect(value).toBe(4);
expect(value).toEqual(4);
});
浮点数的相等,使用 toBeCloseTo 而不是 toEqual,因为你不希望测试取决于一个小小的舍入误差。
js
test('数字匹配 —— 两个浮点数字相加', () => {
const value = 0.1 + 0.2;
//expect(value).toBe(0.3); 这句会报错,因为浮点数有舍入误差
expect(value).toBeCloseTo(0.3); // 这句可以运行
});
字符串匹配
toMatch 后面是正则表达式
js
test('字符串匹配 —— 是否不存在某个字符', () => {
expect('team').not.toMatch(/I/); // 没有I
});
test('字符串匹配 —— 是否存在某个字符', () => {
expect('Christoph').toMatch(/stop/); // 有stop
});
数组和可遍历的对象匹配
通过 toContain来检查一个数组或可迭代对象是否包含某个特定项
js
const shoppingList = [
'diapers',
'kleenex',
'trash bags',
'paper towels',
'milk',
];
test('数组或可迭代对象 —— 数组中是否有 milk ', () => {
expect(shoppingList).toContain('milk');
expect(new Set(shoppingList)).toContain('milk');
});
测试异常匹配
js
function compileAndroidCode() {
throw new Error('you are using the wrong JDK');
}
test('测试异常匹配', () => {
// 函数抛出异常
expect(() => compileAndroidCode()).toThrow();
expect(() => compileAndroidCode()).toThrow(Error);
// 传入字符串,就判断消息的信息是否一致
expect(() => compileAndroidCode()).toThrow('you are using the wrong JDK');
// 正则,抛出的消息里面存在JDK
expect(() => compileAndroidCode()).toThrow(/JDK/);
});
其他API
还有好多的 匹配器,多用用就可以熟悉。
异步测试
接口准备
- 安装axios
npm i axios
- 去在先的接口模拟 JSONPlaceholder,找到posts100个文章列表的接口
回调方式
- 创建async-demo.js定义接口
js
import axios from 'axios'
export function getPosts(callback) {
axios.get('http://jsonplaceholder.typicode.com/posts')
.then(res => {
callback(res.data)
})
}
getPosts(posts => {
console.log(posts.length)
})
- 创建async-demo.test.js写测试代码,记得一定要加done
js
import { getPosts } from './async-demo'
// 回调方式
test('post length is 100', (done) => {
getPosts(posts => {
expect(posts.length).toBe(100)
// 异步执行结束了
done()
})
})
Promise方式
- 在async-demo.js中返回promise的函数
js
export function getPosts2 () {
return axios.get('http://jsonplaceholder.typicode.com/posts')
.then(res => {
return res.data
})
}
2.在async-demo.test.js写测试代码,这里不加done,但是也要返回promise
js
import { getPosts, getPosts2 } from './async-demo'
// Promise
test('Promise posts 100', () => {
// 务必返回 promise ,这个时候就不需要done参数了
return getPosts2().then(posts => {
expect(posts.length).toBe(100)
})
})
使用API .resolves/.rejects
- 在async-demo.js中返回文章的长度,记得这里也必须使用promise
js
export function getPosts3 () {
return axios.get('http://jsonplaceholder.typicode.com/posts')
.then(res => res.data.length)
}
- 在async-demo.test.js写测试代码,使用这个一定也必须是promise
js
import { getPosts, getPosts2, getPosts3 } from './async-demo'
// .resolves/.rejects
test('.resolves posts 100', () => {
// 务必返回promise
return expect(getPosts3()).resolves.toBe(100)
})
async/await
- 在async-demo.test.js写测试代码,推荐使用这种形式,更加语义化。
js
// async/await
test('async/await posts 100', async () => {
const count = await getPosts3()
expect(count).toBe(100)
})
钩子函数
基本用法
多次重复设置
- boforeEach:每次执行前都执行这个函数
- afterEach:每次执行后都执行这个函数
- 创建counter.js在里面写
js
export class Counter {
constructor () {
this.count = 0
}
increment () {
this.count++
}
decrement () {
this.count--
}
incrementTwo () {
this.count += 2
}
decrementTwo () {
this.count -= 2
}
}
- 创建counter.test.js,里面写了三个函数测试,每次都要new一个Counter类,这样比较麻烦,可以使用beforeEach钩子函数
js
import { Counter } from './counter'
test('Counter', () => {
const counter = new Counter()
expect(counter.count).toBe(0)
})
test('Counter decrement', () => {
const counter = new Counter()
counter.decrement()
expect(counter.count).toBe(-1)
})
test('Counter increment', () => {
const counter = new Counter()
counter.increment()
expect(counter.count).toBe(1)
})
- 定义一个beforeEach,每个测试用例之前都会自动调用这个函数
js
import { Counter } from './counter'
let counter = null
beforeEach(() => {
console.log('beforeEach')
counter = new Counter()
})
afterEach(() => {
console.log('afterEach')
})
test('Counter', () => {
expect(counter.count).toBe(0)
})
test('Counter decrement', () => {
counter.decrement()
expect(counter.count).toBe(-1)
})
test('Counter increment', () => {
counter.increment()
expect(counter.count).toBe(1)
})
通过测试
PASS ./async-demo.test.js (5.026 s) ● Console
console.log beforeEach
at Object.<anonymous>
(counter.test.js:6:11)
console.log afterEach
at Object.<anonymous>
(counter.test.js:11:11)
一次性设置
js
// 所有测试用例执行之前执行
beforeAll(() => {
console.log('beforeAll')
})
// 所有测试用例执行之后执行
afterAll(() => {
console.log('afterAll')
})
::: 执行顺序 beforeAll > beforeEach > afterEach > afterAll :::
作用域
- 在counter.test.js中对测试进行分组
js
describe('Counter group1', () => {
// 加减1
test('Counter decrement', () => {
counter.decrement()
expect(counter.count).toBe(-1)
})
test('Counter increment', () => {
counter.increment()
expect(counter.count).toBe(1)
})
})
describe('Counter group2', () => {
// 加减2
test('Counter decrementTwo', () => {
counter.decrementTwo()
expect(counter.count).toBe(-2)
})
test('Counter incrementTwo', () => {
counter.incrementTwo()
expect(counter.count).toBe(2)
})
})
- 如果在组内,也可以添加 beforeEach,afterEach,beforeAll,afterAll ,如果是组内的,组外的还是依然会执行,当前组的函数只会当前测试执行,group1的不会执行到group2中
js
describe('Counter group1', () => {
// 当前组里面每个测试用例之前都调用
beforeEach(() => {
console.log('group1 before')
})
afterEach(() => {
console.log('group1 after')
})
beforeAll(() => {
console.log('group1 beforeAll')
})
afterAll(() => {
console.log('group1 afterAll')
})
test('Counter decrement', () => {
counter.decrement()
expect(counter.count).toBe(-1)
})
test('Counter increment', () => {
counter.increment()
expect(counter.count).toBe(1)
})
})
describe('Counter group2', () => {
beforeEach(() => {
console.log('group2 before')
})
afterEach(() => {
console.log('group2 after')
})
beforeAll(() => {
console.log('group1 beforeAll')
})
afterAll(() => {
console.log('group1 afterAll')
})
test('Counter decrementTwo', () => {
counter.decrementTwo()
expect(counter.count).toBe(-2)
})
test('Counter incrementTwo', () => {
counter.incrementTwo()
expect(counter.count).toBe(2)
})
})
执行顺序
全局的beforeAll > 组内的 beforeAll > 全局的 beforeEach > 组内的 beforeEach > 组内的afterEach > 全局的afterEach > 组内的afterAll > 全局的 afterAll