Jest是 Facebook 的一套开源的 JavaScript 测试框架, 它自动集成了断言、JSDom、覆盖率报告等开发者所需要的所有测试工具,是一款几乎零配置的测试框架。并且它对同样是 Facebook 的开源前端框架 React 的测试十分友好。
他适用但不局限于使用以下技术的项目:Babel, TypeScript, Node, React, Angular, Vue
Jest的目标是在大部分JavaScript项目上实现开箱即用,无需配置。
构建能够轻松追踪大Object的测试。快照可以独立于测试代码,也可以集成进代码行内。
测试程序在自己的进程并行运算以最大限度地提高性能。
从it
到 expect
- Jest将整个工具包放在一个地方。好书写,好维护,非常方便。
本文中我们使用yarn作为包管理器
yarn add --dev jest
将下面的配置部分添加到你的 package.json 里面:
{
"scripts": {
"test": "jest"
}
}
创建一个 overlap.js
文件,作为被测试模块
/**
* 判断两个区间是否重叠
* @param {*} a
* @param {*} b
*/
function overlap(a, b) {
const [startA, endA] = a;
const [startB, endB] = b;
if (startA < endB && endA > startB) {
return true;
}
return false;
}
module.exports = overlap;
然后,创建一个名为 overlap.test.js 的文件。 这将包含我们的实际测试:
const overlap = require('./overlap');
const a = [5, 10];
test('[5, 10] overlap with [1,4]', () => {
expect(overlap(a, [1,4])).toBe(false);
});
运行yarn test
命令
Jest将打印下面这个消息:
$ jest
PASS algorithm/overlap.test.js
✓ [5, 10] overlap with [1,4] (1 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.182 s
Ran all test suites.
✨ Done in 2.24s.
Jest本身是不支持ES6语法的,为了能够使用ES6的语法特性进行单元测试,我们需要使用Babel。
yarn add --dev babel-jest @babel/core @babel/preset-env
在工程的根目录下创建一个babel.config.js
文件用于配置与你当前Node版本兼容的Babel:
// babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
node: 'current',
},
},
],
],
};
Babel的配置取决于具体的项目使用场景 ,可以查阅Babel官方文档来获取更多详细的信息。
toBe 使用 Object.is
对于比较浮点数相等,使用 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/);
});
toEqual 递归检查对象或数组的每个字段。
在JavaScript中执行异步代码是很常见的。 当你有以异步方式运行的代码时,Jest 需要知道当前它测试的代码是否已完成,然后它可以转移到另一个测试。 Jest有若干方法处理这种情况
最常见的异步模式是回调函数。
默认情况下,Jest 测试一旦执行到末尾就会完成。 那意味着该测试将不会按预期工作:
function fetchData(callback) {
const data = 'peanut butter'
setTimeout(() => {
callback(data);
}, 2000);
}
test('the data is peanut butter', () => {
function callback(data) {
expect(data).toBe('peanut butter2');
}
fetchData(callback);
});
callback未执行测试就结束了
我们可以在test方法的回调函数中传入参数done,Jest会等done回调函数执行结束后结束测试。
test('the data is peanut butter', done => {
function callback(data) {
try {
expect(data).toBe('peanut butter');
done();
} catch (error) {
done(error);
}
}
// 上例中的fetchData函数
fetchData(callback);
});
若done函数从未被调用,测试用例会报超时错误,默认5000ms
Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Error:
若 expect 执行失败,它会抛出一个错误,后面的 done() 不再执行。 若我们想知道测试用例为何失败,我们必须将 expect 放入 try 中,将 error 传递给 catch 中的 done函数。 否则,最后控制台将显示一个超时错误失败,不能显示我们在 expect(data) 中接收的值。
test回调函数,必须返回一个promise,如果return,那么就会出现promise在settled之前,测试用例就已经结束。
// 测试promise
function fetchData(type) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (type === 200) {
resolve('peanut butter');
} else {
reject('error')
}
}, 2000);
});
}
test('the data is peanut butter', () => {
return fetchData(200).then(data => {
expect(data).toBe('peanut butter');
})
});
如果你期望一个promise被rejected,可以使用.catch方法,需要确保添加expect.assertions
来验证一定数量的断言被调用。
test('the fetch fails with an error', () => {
expect.assertions(1);
return fetchData().catch(e => expect(e).toMatch('error'));
});
您也可以在 expect 语句中使用 .resolves 匹配器,Jest 将等待此 Promise 决议。 如果承诺被拒绝,则测试将自动失败。
test('the data is peanut butter', () => {
return expect(fetchData(200)).resolves.toBe('peanut butter');
});
test('the fetch fails with an error', () => {
return expect(fetchData()).rejects.toMatch('error');
});
我们也可以在测试中使用 async 和 await
test('the data is peanut butter', async () => {
const data = await fetchData(200);
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');
}
});
expect.assertions(number) 可以校验测试过程中是否调用了一定数量的断言
断言是编程术语,表示为一些布尔表达。
编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设。程序员相信在程序中的某个特定点该表达式值为真,可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言而在部署时禁用断言。同样,程序投入运行后,最终用户在遇到问题时可以重新启用断言。
function fetchData(type) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (type === 200) {
resolve('peanut butter');
} else {
reject('error')
}
}, 2000);
});
}
test('doAsync calls both callbacks', () => {
expect.assertions(2);
const promise1 = fetchData(200);
const promise2 = fetchData(200);
return Promise.all([
promise1.then((data) => {
expect(data).toBe('peanut butter');
}),
promise2.then((data)=> {
expect(data).toBe('peanut butter');
})
])
});
通过expect.assertions(2);
确保promise1,promise2里的断言都被调用了,注释任一一个expect断言,都会提示缺少断言。
所以在测试异步代码时,该方法可以保证回调内的断言都被调用了。
describe可以将测试分组,产生作用域,当 before 和 after 的块在 describe 块内部时,则其只适用于该 describe 块内的测试。before、after函数定义和用法见jest setup-teardown
Jest 会在所有真正的测试开始之前执行测试文件里所有的 describe 处理程序(handlers)
当 describe 块运行完后,,默认情况下,Jest 会按照 test 出现的顺序(译者注:原文是in the order they were encountered in the collection phase)依次运行所有测试,等待每一个测试完成并整理好,然后才继续往下走。
示例:
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
test.only可以告诉jest当前测试文件中仅运行该用例
test.only('this will be the only test that runs', () => {
expect(true).toBe(false);
});
test('this test will not run', () => {
expect('A').toBe('A');
});
文件中没有测试脚本所导致的
Jest会自动找到项目中所有使用.spec.js或.test.js文件命名的测试文件并执行, 确保所有测试文件中都包含测试脚本
import overlap from './overlap.babel';
^^^^^^
SyntaxError: Cannot use import statement outside a module
at Runtime._execModule (node_modules/jest-runtime/build/index.js:1179:56)
jest默认不支持es6语法,可以通过使用Babel来支持