Jest 测试框架基本用法
一、Jest 的安装及配置
1、安装
yarn add --dev jest
在 package.json 中添加:
{
"scripts": {
"test": "jest"
}
}
2、基本用法
- sum.js
- sum.test.js
function sum(a, b) {
return a + b;
}
module.exports = sum;
使用 expect 和 toBe 来测试两值是否一致:
const sum = require('./sum');
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
运行 yarn test
,终端输出:
Jest 会自动运行匹配到的测试文件,其默认匹配规则为:
- 匹配
__test__
文件夹下的 .js 文件(包括 .jsx .ts .tsx) - 匹配所有后缀为 .test.js 或 .spec.js 的文件(包括 .jsx .ts .tsx)
可通过根目录下的 jest.config.js 匹配规则:
module.exports = {
testMatch: [ // glob 格式
"**/__tests__/**/*.[jt]s?(x)",
"**/?(*.)+(spec|test).[jt]s?(x)"
],
// 正则表达式格式,与 testMatch 互斥,不能同时声明
// testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.[jt]sx?$',
};
3、支持 ES6 语法特性
如果要在测试文件中使用 ES6 的 import,需要使用 Babel 安装所需依赖:
yarn add --dev babel-jest @babel/core @babel/preset-env
然后在根目录下创建一个 babel.config.js
配置以下内容:
module.exports = {
presets: [['@babel/preset-env', {targets: {node: 'current'}}]],
};
如果运行 yarn add
出现以下错误:
把 babel.config.js
后缀改成 .cjs
即可。到这里,就可以使用 export〡import 的方式测试函数/组件了。
4、TS 支持
yarn add --dev @babel/preset-typescript
module.exports = {
presets: [
['@babel/preset-env', {targets: {node: 'current'}}],
'@babel/preset-typescript',
],
};
二、Jest 常用的匹配器
1、相等
toBe() 用来测试两个值是否相等:
expect(2 + 2).toBe(4);
toEqual() 会递归检查数组或对象的每个字段,用来测试两个对象是否相等:
const data = {one: 1};
data['two'] = 2;
expect(data).toEqual({one: 1, two: 2});
2、数字
数字大小的比较可以通过以下匹配器来进行匹配:
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);
});
对于浮点数的比较,需使用 toBeCloseTo 而非 toBe,否则会导致舍入误差:
test('两个浮点数字相加', () => {
const value = 0.1 + 0.2;
// expect(value).toBe(0.3); 这句会报错,因为浮点数有舍入误差
expect(value).toBeCloseTo(0.3); // 这句可以运行
});
3、取反
not 可以表达相反匹配:
expect(a + b).not.toBe(0);
4、真值
可以使用以下真值判断 undefined, null, false 等:
- toBeNull 只匹配 null
- toBeUndefined 只匹配 undefined
- toBeDefined 与 toBeUndefined 相反
- toBeTruthy 匹配任何 if 语句为真
- toBeFalsy 匹配任何 if 语句为假
test('null', () => {
const n = null;
expect(n).toBeNull();
expect(n).toBeDefined();
expect(n).not.toBeUndefined();
expect(n).not.toBeTruthy();
expect(n).toBeFalsy();
});
test('zero', () => {
const z = 0;
expect(z).not.toBeNull();
expect(z).toBeDefined();
expect(z).not.toBeUndefined();
expect(z).not.toBeTruthy();
expect(z).toBeFalsy();
});
5、包含
可以通过 toContain 来检查一个数组或可迭代对象是否包含某个特定项:
const shoppingList = [
'diapers',
'kleenex',
'trash bags',
'paper towels',
'milk',
];
test('shoppingList 数组中包含 milk', () => {
expect(shoppingList).toContain('milk');
expect(new Set(shoppingList)).toContain('milk');
});
三、模拟函数
mock 模拟函数可以用来测试事件是否被正常调用:
it('onClick', () => {
const clickFn = jest.fn();
const { container } = render(<Button onClick={clickFn} />);
fireEvent.click(container.firstChild);
expect(clickFn).toHaveBeenCalled();
});
除了模拟空函数,还可以模拟传参确保如期调用的函数:
const mockCallback = jest.fn(x => 42 + x);
forEach([0, 1], mockCallback);
// 此 mock 函数被调用了两次
expect(mockCallback.mock.calls.length).toBe(2);
// 第一次调用函数时的第一个参数是 0
expect(mockCallback.mock.calls[0][0]).toBe(0);
// 第二次调用函数时的第一个参数是 1
expect(mockCallback.mock.calls[1][0]).toBe(1);
// 第一次函数调用的返回值是 42
expect(mockCallback.mock.results[0].value).toBe(42);
四、测试异步函数
1、Promise
为测试返回一个 Promise,Jest 会等待 Promise 的 resolve 状态,如果 Promise 的状态变为 rejected, 测试将会失败。
test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
2、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、await 和 .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.toMatch('error');
});
3、回调
test 第二个参数可以传入 done,此时 Jest 会等 done
回调函数被调用执行后,再结束测试:
test('the data is peanut butter', done => {
function callback(error, data) {
if (error) {
done(error);
return;
}
try {
expect(data).toBe('peanut butter');
done();
} catch (error) {
done(error);
}
}
fetchData(callback);
});
五、测试钩子
1、重复运行
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();
});
2、一次性运行
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();
});
3、作用域
可以通过 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
// 2 - beforeEach
// 2 - test
// 2 - afterEach
// 1 - afterEach
// 2 - afterAll
// 1 - afterAll