安装Jest
安装Jest
yarn add --dev jest
安装Bable
yarn add --dev babel-jest @babel/core @babel/preset-env
在根目录创建bable
配置文件
// babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
node: 'current',
},
},
],
],
};
如果不安装babel
,Jest
测试文件中需要使用commonJS
不兼容es
标准,需要使用babel
才可以正常使用es
模块导入导出
匹配器
普通匹配器
toBe(...)
匹配器
test('two plus two is four', () => {
expect(2 + 2).toBe(4);
});
在这段代码中,expect(2 + 2)
返回一个“期望”的对象,通常不会对期望对象调用过多的匹配器,在这里,使用的是toBe(...)
匹配器,当Jest
运行时,它会跟踪所有失败的匹配器,并打印出详细的错误信息
test(...)
表示这是一个测试用例,第一个参数为测试的描述,第二个参数是测试执行的函数,上述测试,用白话来看就是:期望 2 + 2的结果应该是 4
toEqual(...)
匹配器
toBe
只是简单的使用Object.is()
进行判断,在复杂值判断时就不会正确,这时候需要使用toEqual
来判断,toEqual
会递归判断对象或数组的每一个字段
test('object assignment', () => {
const data = {one: 1};
data['two'] = 2;
expect(data).toEqual({one: 1, two: 2});
});
not
匹配器
not
匹配器能够对结果取反
test('adding positive numbers is not zero', () => {
for (let a = 1; a < 10; a++) {
for (let b = 1; b < 10; b++) {
expect(a + b).not.toBe(0);
}
}
});
真值
测试时有时候需要区分undefined
、null
、false
,有时又不需要区分,Jest
对此提供了相应的匹配器
test('null', () => {
const n = null;
expect(n).toBeNull(); // 为null,通过
expect(n).toBeDefined(); // 为null,不是undefined,通过
expect(n).not.toBeUndefined(); // 不为undefined,通过
expect(n).not.toBeTruthy(); // 不为真,通过
expect(n).toBeFalsy(); // 为假,通过
});
test('zero', () => {
const z = 0;
expect(z).not.toBeNull(); // 不为null,通过
expect(z).toBeDefined(); // 不是undefined,通过
expect(z).not.toBeUndefined(); // 同上
expect(z).not.toBeTruthy(); // 不为真,通过
expect(z).toBeFalsy(); // 为假,通过
});
toBeNull
只匹配null
toBeUndefined
只匹配undefined
toBeDefined
与toBeUndefined
相反toBeTruthy
匹配任何if
语句为真toBeFalsy
匹配任何if
语句为假
数字
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 and toEqual are equivalent for numbers
expect(value).toBe(4);
expect(value).toEqual(4); //对于数字类型来讲,toBe和toEqual效果是相同的
});
在javascript
中,浮点类型判断也许会有误差,如常知的0.1 + 0.2 != 0.3
,这时候需要使用toBeCloseTo
来进行匹配
test('两个浮点数字相加', () => {
const value = 0.1 + 0.2;
//expect(value).toBe(0.3); 这句会报错,因为浮点数有舍入误差
expect(value).toBeCloseTo(0.3); // 这句可以运行
});
字符串
toMatch
匹配器
test('there is no I in team', () => {
expect('team').not.toMatch(/I/);
});
test('but there is a "stop" in Christoph', () => {
expect('Christoph').toMatch(/stop/);
});
toMatch
匹配器可以对字符串进行正则匹配
数组和可迭代对象
toContain
匹配器
const shoppingList = [
'diapers',
'kleenex',
'trash bags',
'paper towels',
'beer',
];
test('the shopping list has beer on it', () => {
expect(shoppingList).toContain('beer');
expect(new Set(shoppingList)).toContain('beer');
});
toContain
匹配器可以判断一个数组或可迭代对象中是否包含某个特定项
抛出异常
toThrow
匹配器
function compileAndroidCode() {
throw new Error('you are using the wrong JDK');
}
test('compiling android goes as expected', () => {
expect(compileAndroidCode).toThrow(); // 匹配错误
expect(compileAndroidCode).toThrow(Error); // 匹配Error类型错误
// You can also use the exact error message or a regexp
expect(compileAndroidCode).toThrow('you are using the wrong JDK'); //匹配错误message
expect(compileAndroidCode).toThrow(/JDK/); // 可以使用正则匹配
});
如果测试的特定的函数需要抛出一个错误,需要使用toThrow
匹配器
测试异步代码
回调函数
test('the data is peanut butter', done => {
function callback(data) {
expect(data).toBe('peanut butter');
done(); // 标志回调函数执行完毕
}
fetchData(callback);
});
test
函数接收一个函数参数,执行函数表示回调函数执行完毕,测试结束,如果done()
没有被调用执行,那么这个测试将会失败
Promise
更简单的方式是使用Promise
,Jest
会等待Promise
执行完毕,如果Promise
执行reject
,测试将失败
test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
.resolves
可以用resolves
匹配器来匹配Promise
的resolve
test('the data is peanut butter', () => {
return expect(fetchData()).resolves.toBe('peanut butter');
});
.rejects
如果想要Promise
被reject
,可以使用.rejects
匹配器
test('the fetch fails with an error', () => {
return expect(fetchData()).rejects.toMatch('error');
});
async/await
最简单的方法是使用async
和await
test('the data is peanut butter', async () => {
const data = await fetchData();
expect(data).toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
expect.assertions(1);
try {
await fetchData();
} catch (e) {
expect(e).toMatch('error');
}
});
你也可以将async
和.resolves
、'.rejects'一起使用
test('the data is peanut butter', async () => {
await expect(fetchData()).resolves.toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
await expect(fetchData()).rejects.toThrow('error');
});
设置测试前置和后置任务
某些测试用例,需要在运行测试时进行一些准备工作,如类实例的初始化,测试结束后的某些操作
beforeEach
、afterEach
每次测试设置
beforeEach(() => {
initializeCityDatabase();
});
afterEach(() => {
clearCityDatabase();
});
test('city database has Vienna', () => {
expect(isCity('Vienna')).toBeTruthy();
});
test('city database has San Juan', () => {
expect(isCity('San Juan')).toBeTruthy();
});
beforeEach
在每一个测试用例开始测试之前执行,如上每次测试之前会执行initializeCityDatabase()
afterEach
则是在每一个测试用例测试结束后执行,如上每次测试之后会执行clearCityDatabase()
beforeEach
、afterEach
处理异步代码的方式和test
一样,可以在异步代码执行完毕后调用done()
函数,也可以返回一个Promise
让Jest
处理
beforeAll
、afterAll
一次性设置
某些情况,如异步初始化数据库,只需要在某一组(简单来讲可以是一个*.test.js
文件)测试之前和结束时进行设置,可以使用beforeAll
、afterAll
beforeAll(() => {
return initializeCityDatabase();
});
afterAll(() => {
return clearCityDatabase();
});
test('city database has Vienna', () => {
expect(isCity('Vienna')).toBeTruthy();
});
test('city database has San Juan', () => {
expect(isCity('San Juan')).toBeTruthy();
});
作用域
默认情况下,before
和after
会以文件为作用域作用于每次都测试,你也可以使用describe
来将测试分组,当before
和after
包含在describe
块中时,它们就只作用于describe
块中的测试用例
beforeAll(() => console.log('1 - beforeAll'));
afterAll(() => console.log('1 - afterAll'));
beforeEach(() => console.log('1 - beforeEach'));
afterEach(() => console.log('1 - afterEach'));
test('', () => console.log('1 - test'));
describe('Scoped / Nested block', () => {
beforeAll(() => console.log('2 - beforeAll'));
afterAll(() => console.log('2 - afterAll'));
beforeEach(() => console.log('2 - beforeEach'));
afterEach(() => console.log('2 - afterEach'));
test('', () => console.log('2 - test'));
});
// 1 - beforeAll
// 1 - beforeEach
// 1 - test
// 1 - afterEach
// 2 - beforeAll
// 1 - beforeEach —— describe 中的测试依旧会执行最外层`before`
// 2 - beforeEach
// 2 - test
// 2 - afterEach
// 1 - afterEach
// 2 - afterAll
// 1 - afterAll
需要注意的是,最外层的before
和after
仍然会作用于describe
块中的测试
describe
和test
块的执行顺序
Jest
会在所有真正的测试开始之前执行测试文件里所有的describe
,所以任务写在before*
和after*
而不是直接卸载describe
中,当所有describe
执行完毕后,Jest
会按照test
出现顺序依次执行
describe('outer', () => {
console.log('describe outer-a');
describe('describe inner 1', () => {
console.log('describe inner 1');
test('test 1', () => {
console.log('test for describe inner 1');
expect(true).toEqual(true);
});
});
console.log('describe outer-b');
test('test 1', () => {
console.log('test for describe outer');
expect(true).toEqual(true);
});
describe('describe inner 2', () => {
console.log('describe inner 2');
test('test for describe inner 2', () => {
console.log('test for describe inner 2');
expect(false).toEqual(false);
});
});
console.log('describe outer-c');
});
// describe outer-a
// describe inner 1
// describe outer-b
// describe inner 2
// describe outer-c
// test for describe inner 1
// test for describe outer
// test for describe inner 2