jest使用
一、起步 #
1、挂载
// 方法一
const Constructor = Vue.extend(monitor);
const vm = new Constructor();
// 方法二
const monitor = mount(monitor, { // 现在挂载组件,你便得到了这个包裹器
mocks: { //模拟数据
$store
}
});
const vm = monitor.vm;
2、生成jest.config.js
配置文件
- 安装相关依赖
npm install jest --global
jest --init
// 安装babel相关
npm install --save-dev babel-jest @babel/core @babel/preset-env
- babel配置
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {targets: {node: 'current'}}],
],
};
- jest通过babel支持ts
npm install --save-dev @babel/preset-typescript
// 并在babel.config.js中添加
presets: [
...
'@babel/preset-typescript',
...
],
- 由于Babel对TypeScript的支持是编译,但是Jest在运行测试时不会进行类型检查。如果需要,可以使用ts-jest
二、常用匹配器 #
- expect(完整列表)
- 在此代码中,
expect (2 + 2)
返回一个"期望"的对象。 你通常不会对这些期望对象调用过多的匹配器。 在此代码中,.toBe(4)
是匹配器。 当 Jest 运行时,它会跟踪所有失败的匹配器,以便它可以为你打印出很好的错误消息。
test('two plus two is four', () => {
expect(2 + 2).toBe(4);
});
toBe
用来测试精确相等,如果想要检查对象的值,使用toEqual
代替,toEqual
递归检查对象或数组的每个字段- 匹配相反的测试值
expect(a + b).not.toBe(0);
-
Truthiness:在测试中,你有时需要区分 undefined、 null,和 false,但有时你又不需要区分。 Jest 让你明确你想要什么。
- 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(); });
- 数字
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而不是toEqual
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/);
});
- 数组和可迭代,可以通过
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');
});
-
例外情况
测试某个特定函数在调用时抛出错误,使用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);
// You can also use the exact error message or a regexp
expect(compileAndroidCode).toThrow('you are using the wrong JDK');
expect(compileAndroidCode).toThrow(/JDK/);
});
三、测试异步代码 #
在JavaScript中执行异步代码是很常见的。 当你有以异步方式运行的代码时,Jest 需要知道当前它测试的代码是否已完成,然后它可以转移到另一个测试。 Jest有若干方法处理这种情况。
-
回调
最常见的异步模式是回调函数。
例如,假设您有一个fetchData(callback)
函数,获取一些数据并在完成时调用callback(data)
。你想要测试此返回的数据是否为字符串'peanut butter'
.
默认情况下,Jest 测试一旦执行到末尾就会完成。 那意味着该测试将不会按预期工作:
// 不要这样做!
test('the data is peanut butter', () => {
function callback(data) {
expect(data).toBe('peanut butter');
}
fetchData(callback);
});
问题在于一旦fetchData
执行结束,此测试就在没有调用回调函数前结束。
还有另一种形式的test,解决此问题。 使用单个参数调用 done,而不是将测试放在一个空参数的函数。 Jest会等done回调函数执行结束后,结束测试。
test('the data is peanut butter', done => {
function callback(data) {
expect(data).toBe('peanut butter');
done();
}
fetchData(callback);
});
如果 done()
永远不会调用,这个测试将失败,这也是你所希望发生的。
-
Promises
如果你的代码使用Promise,则有一种更简单的方法来处理异步测试。从测试中返回一个承诺,Jest将等待该承诺解决。如果承诺被拒绝,则测试将自动失败。
举个例子,如果fetchData
不使用回调函数,而是返回一个 Promise,其解析值为字符串'peanut butter'
我们可以这样测试:
// 测试异步调用
function fetchData(resolve, reject) {
setTimeout(() => {
return resolve("peanut butter");
}, 2000);
}
let fetch1 = new Promise(fetchData);
test('the data is peanut butter', () => {
return fetch1.then(data => {
expect(data).toBe('peanut butter');
});
});
-
.resolves / .rejects
您也可以在 expect 语句中使用.resolves
匹配器,Jest 将等待此 Promise 解决。 如果承诺被拒绝,则测试将自动失败。
test('the data is peanut butter', () => {
return expect(fetchData()).resolves.toBe('peanut butter');
});
一定不要忘记把整个断言作为返回值返回⸺如果你忘了return语句的话,在 fetchData 返回的这个 promise 变更为 resolved 状态、then() 有机会执行之前,测试就已经被视为已经完成了。
如果你想要 Promise 被拒绝,使用 .catch 方法。 它参照工程 .resolves 匹配器。 如果 Promise 被拒绝,则测试将自动失败。
test('the fetch fails with an error', () => {
return expect(fetchData()).rejects.toMatch('error');
});
-
Async/Await
可以在测试中使用 async 和 await,要编写异步测试,请在传递给测试的函数前面使用async关键字
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.toThrow('error');
});
在这些情况下,async和await实际上是语法糖,与应诺示例使用的逻辑相同。
上述的诸多形式中没有哪个形式特别优于其他形式,你可以在整个代码库中,甚至也可以在单个文件中混合使用它们。 这只取决于哪种形式更能使您的测试变得简单。
三、Setup and Teardown #
写测试的时候你经常需要在运行测试前做一些准备工作,和在运行测试后进行一些整理工作。 Jest 提供辅助函数来处理这个问题。
-
为多次测试重复设置
如果你有一些要为多次测试重复设置的工作,你可以使用 beforeEach 和 afterEach。
例如,我们考虑一些与城市信息数据库进行交互的测试。 你必须在每个测试之前调用方法 initializeCityDatabase() ,同时必须在每个测试后,调用方法 clearCityDatabase()。 你可以这样做:
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和afterEach能够通过与 异步代码测试 相同的方式处理异步代码 — — 他们可以采取 done
参数或返回一个 promise。 例如,如果 initializeCityDatabase()
返回解决数据库初始化时的 promise ,我们会想返回这一 promise︰
beforeEach(() => {
return initializeCityDatabase();
});
-
一次性设置
在某些情况下,你只需要在文件的开头做一次设置。当设置异步时,这尤其麻烦,因此您不能内联. Jest 提供beforeAll
和afterAll
处理这种情况。
例如,如果initializeCityDatabase
和clearCityDatabase
都返回了 promise ,城市数据库可以在测试中重用,我们就能把我们的测试代码改成这样:
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 块内的测试
注意,顶级的 beforeEach 在 describe 块级的 beforeEach 之前被执行。 这可能有助于说明所有钩子的执行顺序
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
-
desribe和test块的执行顺序
Jest 会在所有真正的测试开始之前执行测试文件里所有的describe
处理程序(handlers)。 这是在before*
和after*
处理程序里面 (而不是在 describe 块中)进行准备工作和整理工作的另一个原因。 当 describe 块运行完后,,默认情况下,Jest 会按照 test 出现的顺序(译者注:原文是in the order they were encountered in the collection phase)依次运行所有测试,,等待每一个测试完成并整理好,然后才继续往下走 -
通用建议
如果测试失败,第一件要检查的事就是,当仅运行这条测试时,它是否仍然失败。要仅使用Jest运行一个测试,请将该test
命令临时更改为test.only
test.only('this will be the only test that runs', () => {
expect(true).toBe(false);
});
test('this test will not run', () => {
expect('A').toBe('A');
});
如果你有一个测试,当它作为一个更大的用例中的一部分时,经常运行失败,但是当你单独运行它时,并不会失败,所以最好考虑其他测试对这个测试的影响。 通常可以通过修改 beforeEach 来清除一些共享的状态来修复这种问题。如果不确定是否正在修改某些共享状态,也可以尝试使用beforeEach
记录数据。