Skip to main content

Jest 测试框架基本用法

一、Jest 的安装及配置

1、安装

yarn add --dev jest

package.json 中添加:

package.json
{
"scripts": {
"test": "jest"
}
}

2、基本用法

sum.js
function sum(a, b) {
return a + b;
}
module.exports = sum;

运行 yarn test,终端输出:

Jest 会自动运行匹配到的测试文件,其默认匹配规则为:

  • 匹配 __test__ 文件夹下的 .js 文件(包括 .jsx .ts .tsx)
  • 匹配所有后缀为 .test.js 或 .spec.js 的文件(包括 .jsx .ts .tsx)

可通过根目录下的 jest.config.js 匹配规则:

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 配置以下内容:

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
babel.config.js
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 等:

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、一次性运行

beforeAllafterAll 可以在文件中所有测试用例开始前和结束后一次性执行,而不用每个测试用例都执行一遍,从而影响性能:

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