前端测试 Jest (未完待续)
1 配置环境
- node npm install --save-dev jest 需在环境变量中配置
- yarn yarn add --dev jest yarn global add jest
2 demo
jest 的工程文件后缀都是xx.test.js,我们在运行一个jest工程的时候,jest会自动查找运行的后缀为*.test.js的脚本进行单元测试。
machers 众多的匹配判断器
- toBe() 用于检验基本数据类型的值是否相等
- toEqual() 用于检验引用数据类型的值,由于js本身object数据类型的本身特性,引用数据类型对比只是指针的对比,但是需要对比对象的每个值,所以这时候用到的是toEqual()
- Truthiness 布尔值判断的匹配器
- toBeNull 只匹配 null
- toBeUndefined 只匹配 undefined
- toBeDefined 与 toBeUndefined 相反
- toBeTruthy 匹配任何 if 语句为真
- toBeFalsy 匹配任何 if 语句为假
- 数字匹配器 用于判断数字值之间的对比
- toBeGreaterThan 大于匹配器
- toBeGreaterThanOrEqual 大于等于匹配器
- toBeLessThan 小于匹配器
- toBeLessThanOrEqual 小于等于匹配器
- tobe 和 toequal 都是等价功能相同的对于数字
- toMatch 字符串匹配器 和字符串的match相同
- toContain 数组匹配器 用于判断数组中是否包含某些值
- toThrow 报错匹配器 用于测试特定的抛出错误,可以判断报错语句的文字(支持正则匹配),也可以判断报错类型。
异步代码的判断
js 基于单线程异步的特性,在代码中充斥的异步程序和各种回调,所以我们在测试异步代码的时候要格外注意。一下是各种异步的案例
- callback 回调
test('the data is peanut butter', () => {
function callback(data) {
expect(data).toBe('peanut butter');
}
fetchData(callback);
});
// 这种情况有问题,大家都知道一个ajax请求是一个事件(由两个事务发送和返回组成),当发送成功和返回成功这个事件就是完成的,就是成功的,这个时候jest就认为ajax事件完成了,所以检验会在回调判断执行前结束,没有验证data数据,所以这是有问题的。
Jest 提供了相应的方法来done来解决这样的问题,确保回调执行的来完成整个test验证。 使用单个参数调用 done,而不是将测试放在一个空参数的函数。Jest会等done回调函数执行结束后,结束测试。 let's show code
test('the data is peanut butter', done => {
function callback(data) {
expect(data).toBe('peanut butter');
done();
}
fetchData(callback);
});
如果 done()永远不会调用,这个测试将失败,这也是你所希望发生的。
- Promise 代码使用Promise处理异步程序的话,我们有更简单的方法进行异步测试。如果Promise返回的状态是reject则测试自动失败。
test('Promise test', ()=>{
// 断言测试
expect.assertions(1);
// 一定记得return,否则测试拿不到promise的状态,测试会在fetchData()完成前完成。
return fetchData().then(data=>{
expect(data).tobe('promise')
})
})
如果期望的promise是reject,我们可以通过catch来捕获我们的错误。 请确保添加 expect.assertions 来验证一定数量的断言被调用。 否则一个fulfilled态的 Promise 不会让测试失败。
test('the fetch fails with an error', () => {
expect.assertions(1);
return fetchData().catch(e => expect(e).toMatch('error'));
});
- .resolves / .rejects 匹配器 Jest有特殊的处理promise的匹配器,resolves和rejects。使用resolves匹配器处理promise,如果返回的状态是reject则测试将自动失败。切记不要丢失return。同理,你用rejects匹配器处理,如果你想得到promise返回是reject但是promise成功了返回一个成功的状态resolve(fulfilled),则测试结果是失败的。
// let's show code~
// resolves
test('the data is peanut butter', () => {
expect.assertions(1);
return expect(fetchData()).resolves.toBe('peanut butter');
});
// rejects
test('the fetch fails with an error', () => {
expect.assertions(1);
return expect(fetchData()).rejects.toMatch('error');
});
- Async/Await 你也可以使用async/await 处理异步测试。只需要在test的回调函数前面添加async关键字。
// 提前了解async和await的使用方法和场景
test('the data is peanut butter', async () => {
// 切记添加断言测试
expect.assertions(1);
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');
}
});
还可以混合使用.resolves / .rejects和Async/Await
// 因为await本身返回的就是一个promise。所以我们可以在返回的promise的基础之上继续测试我们的代码。
test('the data is peanut butter', async () => {
expect.assertions(1);
await expect(fetchData()).resolves.toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
expect.assertions(1);
await expect(fetchData()).rejects.toMatch('error');
});
#Jest的遍历 和 Setup 和 Teardown 进行一些重复的test设置的时候,我们可以使用beforeEach和afterEach。假设我们在一个交互数据上需要重复的做一些测试设置。假如测试前调用initializeCityDatabase()和测试后调用clearCityDatabase()。那我们就可以使用beforeEach和afterEach了。 let's show code~
beforeEach and 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 和 afterEach处理异步的代码,同样可以采用done和promise,如果initializeCityDatabase()返回的是一个promise我们可以这样处理他。let's show code
beforeEach(()=>{
return initializeCityDatabase(); // return一个promise
})
beforeAll and afterAll
Scoping
我们可以通过Jest的describe函数,通过js函数式编程的思想来创建一个作用域,来模块化我们的测试。具体场景就是的通过describe来模块我们的foreach的作用范围。 let's show code
// 这里的beforeEach适用于所有的test
beforeEach(() => {
return initializeCityDatabase();
});
test('city database has Vienna', () => {
expect(isCity('Vienna')).toBeTruthy();
});
test('city database has San Juan', () => {
expect(isCity('San Juan')).toBeTruthy();
});
describe('matching cities to foods', () => {
// 这里beforeEach仅仅适用于describe中的测试
beforeEach(() => {
return initializeFoodDatabase();
});
test('Vienna <3 sausage', () => {
expect(isValidCityFoodPair('Vienna', 'Wiener Schnitzel')).toBe(true);
});
test('San Juan <3 plantains', () => {
expect(isValidCityFoodPair('San Juan', 'Mofongo')).toBe(true);
});
});
既然这么多beforeEach/afterEach和beforeAll/afterAll,那结合descibe来看看我们的执行顺序。相信大家看完以后会想起js的(任务队列和作用域)。
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
正如结果查看顺序,全局的会作用到describe内部。 注意执行顺序,beforeALL和afterAll只会执行一次, 全局的beforeEach作用到了describe中的test,还要注意的是after作用域中的after比全局的after先执行。
beforeAll 在beforeEach前先执行,而afterAll在afterEach后执行。
如果你想在众多的test中,仅仅想进行其中的一个test,很简单,你只要在他的test语句中添加.only标识就可以了,其他的测试则会跳过了.let's show code
// 只有这个test会跑
test.only('this will be the only test that runs', () => {
expect(true).toBe(false);
});
// 这个test则会跳过
test('this test will not run', () => {
expect('A').toBe('A');
});