JEST -- 单元测试框架
由 Facebook 推出,目前 Babel,TypeScript,Node,React,Angular,Vue 等都在使用
安装
yarn add --dev jest
或
npm install --save-dev jest
运行
全局安装 jest 时,使用“jest 文件”运行 没有全局安装 jest(环境变量没有 jest)时在 package.json 中添加命令“jest 文件”,使用 npm 运行
使用 babel
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
node: 'current',
},
},
],
],
};
快照
第一次执行时将表达式的结果存储到.snap 文件中,之后每次执行将第一次结果与当前进行比较,相同则通过
// index.test.js
test('snap',()=>{
expect(1+2).toMatchSnapshot('desc')
})
//index.test.js.snap
// snap测试块中的第一条测试首次执行的结果
exports[`snap: desc 1`] = `3`;
执行测试的命令中加上--updateSnapshot
更新 snap
自定义快照
// index.test.js
const {toMatchSnapshot} = require('jest-snapshot')
expect.extend({
newSnap(received, l) {
return toMatchSnapshot.call(this, l, 'desc')
}
})
test('snap',()=>{
expect('hhh').newSnap(65)
})
//index.test.js.snap
exports[`snap: desc 1`] = `65`;
mock 函数
测试函数或模块的行为是否正确
创建 mock 函数
function add(a, b) {
return a + b
}
test('mock', () => {
const mockFun = jest.fn(add) // 创建
const arr = [[1, 2], [3, 4]]
arr.forEach(item => mockFun(...item))
console.log(mockFun.mock)
// 此 mock 函数被调用了两次
expect(mockFun.mock.calls.length).toBe(2);
// 第一次调用函数时的第一个参数是 0
expect(mockFun.mock.calls[0][0]).toBe(1);
// 第二次调用函数时的第一个参数是 1
expect(mockFun.mock.calls[1][0]).toBe(3);
// 第一次调用函数时的结果是 3
expect(mockFun.mock.results[0].value).toBe(3);
})
// console结果
// {
// calls: [ [ 1, 2 ], [ 3, 4 ] ],
// instances: [ undefined, undefined ],
// invocationCallOrder: [ 1, 2 ],
// results: [ { type: 'return', value: 3 }, { type: 'return', value: 7 } ]
// }
创建 mock 模块
import axios from 'axios';
jest.mock('axios');
test('should fetch users', () => {
const users = [{name: 'Bob'}];
const resp = {data: users};
axios.get.mockResolvedValue(resp); // 代替axios.get('/user.json').then的回调参数
return axios.get('/user.json').then(resp =>expect(resp.data).toEqual(users))
});
期望 api
普通匹配器
- toBe 比较值的一致性
- toEqual 比较对象的一致性
- not 匹配期望之外的值
- toBeGreaterThan 数字大于
- toBeGreaterThanOrEqual 数字大于等于
- toBeLessThan 数字小于
- toBeLessThanOrEqual 数字小于等于
- toBeCloseTo 浮点数等于(浮点数比较时代替 toBe)
- toContain 数组等可迭代对象包含某个值
expect(1 + 2).not.toBe(4)
expect([1, 2, 3]).toContain(2)
- .toBeDefined() 值不是 undefined
- .toBeUndefined() undefined
- .toBeFalsy() 假值
- .toBeTruthy() 真值
- .toBeNaN() NaN
- .toBeNull() null
expect(0).toBeFalsy()
- .toMatch(string | regexp) 匹配正则
- .toMatchObject(Object) 目标对象是否为接受到的对象的子集,对应属性值相同,深度比较
expect('str').toMatch(/^s/)
expect({a: 1, b: 2}).toMatchObject({a: 1})
代替值的匹配器
在 toEqual 和 toBeCalledWith 中替代
- expect.anything() 匹配除 null 和 undefined 之外的值
- expect.any(constrocutor) 匹配给定构造类型的值
- expect.stringMatching(string | regexp) 匹配字符串或正则
expect(2).toEqual(expect.anything())
expect(2).toEqual(expect.any(Number))
expect('2').toEqual(expect.stringMatching(/2/))
自定义匹配器
使用 expect.extend 在执行测试之前定义
自定义的匹配器必须返回一个对象,该对象中包含 pass 和 message。其中 pass 是 Boolean 类型,表示是否通过,message 是 Function,返回报错信息
匹配器函数可以传多个参数,第一个为 expect 的值,其它为自定义传参
如果没有在全部的代码执行路径设置返回值,对应测试结果会报错
在自定义匹配器中通过 this 对象中的 isNot、promise、equals 等访问 jest 提供的函数
expect.extend({
yourMatcher(x, y, z) {
return {
pass: true,
message: () => '',
};
},
yourMatcher2(x, y, z) {
return {
pass: true,
message: () => '',
};
},
});
异步匹配
在测试块中直接使用异步函数,测试不会等待异步函数的处理结果
以下断言解决不能测试异步函数的问题(并非只用于处理异步函数)
expect.assertions(number) 测试期间调用一定数量的断言
expect.hasAssertions() 测试期间至少调用一次断言
回调中执行测试
该方法中如果没有在代码执行路径上调用 done,最后会报超时(等待时间为 test 中的第三个参数 timeout)
function fetchData(cb) {
cb('peanut butter')
}
test('the data is peanut butter', done => {
function callback(data) {
try {
expect(data).toBe('peanut butter');
done();
} catch (error) {
done(error);
}
}
fetchData(callback);
});
- 使用 promise
// fetchData返回promise
test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
- 使用 async/await
function get(){
return new Promise(res=>res('Mark'))
}
test('snap', async ()=>{
const data = await get()
expect(data).toEqual('Mark')
})
全局 api
test(name, fn, timeout) 执行测试,timeout 可选,指定终止测试前的等待时间
test.each(table)(name, fn, timeout) 多组数据执行同一个测试
test.each([ [1, 1, 2],
[1, 2, 3],
[2, 1, 3],
])('.add(%i, %i)', (a, b, expected) => {
expect(a + b).toBe(expected);
});
- describe(name, fn) 将多个 test 包装成一个(非必须),内部是一个独立作用域
describe('all', () => {
test('test1', () => {
expect(1).toBeTruthy()
});
test('test2', () => {
expect(0).toBeFalsy()
});
});
- describe.each(table)(name, fn, timeout) 多组数据执行多个测试
describe.each([
[1, 1, 2],
[1, 2, 3],
[2, 1, 3],
])('.add(%i, %i)', (a, b, expected) => {
test(`returns ${expected}`, () => {
expect(a + b).toBe(expected);
});
test(`returned value not be greater than ${expected}`, () => {
expect(a + b).not.toBeGreaterThan(expected);
});
test(`returned value not be less than ${expected}`, () => {
expect(a + b).not.toBeLessThan(expected);
});
});
不能在 test 中内嵌 test 或 describe
describe 中可以嵌套 describe
有多个 describe 和 test 时的执行顺序:先执行 describe 中除 test 之外的代码,全部执行完之后,按照 test 在代码上出现的顺序执行。
同一个 test 中如果有测试用例出错,不会执行该 test 剩余的测试用例,但仍会执行其他 test。
- afterAll(fn, timeout) 同个测试块(describe)中所有 test执行结束之后调用
// 以下console顺序为
// 1
// 5
// 6
let a = 1
describe('all', () => {
test('inner', () => {
console.log(a)
});
afterAll(()=>{
a = 5
})
});
test('outer', ()=>{
console.log(a)
a = 6
})
afterAll(()=>{
console.log(a)
})
- afterEach(fn, timeout) 同个测试块(describe)中每个 test执行结束之后调用
- beforeAll(fn, timeout) 同个测试块(describe)中所有 test执行开始之前调用
- beforeEach(fn, timeout) 同个测试块(describe)中每个 test执行开始之前调用
本文使用 mdnice 排版