ZHANGYU.dev

October 14, 2023

Jest 学习笔记(一)

JavaScript7.5 min to read

安装Jest

安装Jest

yarn add --dev jest

安装Bable

yarn add --dev babel-jest @babel/core @babel/preset-env

在根目录创建bable配置文件

// babel.config.jsmodule.exports = {  presets: [    [      '@babel/preset-env',      {        targets: {          node: 'current',        },      },    ],  ],};

如果不安装babelJest测试文件中需要使用commonJS不兼容es标准,需要使用babel才可以正常使用es模块导入导出

匹配器

普通匹配器

toBe(...)匹配器

test('two plus two is four', () => {  expect(2 + 2).toBe(4);});

在这段代码中,expect(2 + 2)返回一个“期望”的对象,通常不会对期望对象调用过多的匹配器,在这里,使用的是toBe(...)匹配器,当Jest运行时,它会跟踪所有失败的匹配器,并打印出详细的错误信息

test(...)表示这是一个测试用例,第一个参数为测试的描述,第二个参数是测试执行的函数,上述测试,用白话来看就是:期望 2 + 2的结果应该是 4

toEqual(...)匹配器

toBe只是简单的使用Object.is()进行判断,在复杂值判断时就不会正确,这时候需要使用toEqual来判断,toEqual会递归判断对象或数组的每一个字段

test('object assignment', () => {  const data = {one: 1};  data['two'] = 2;  expect(data).toEqual({one: 1, two: 2});});

not匹配器

not匹配器能够对结果取反

test('adding positive numbers is not zero', () => {  for (let a = 1; a < 10; a++) {    for (let b = 1; b < 10; b++) {      expect(a + b).not.toBe(0);    }  }});

真值

测试时有时候需要区分undefinednullfalse,有时又不需要区分,Jest对此提供了相应的匹配器

test('null', () => {  const n = null;  expect(n).toBeNull(); // 为null,通过  expect(n).toBeDefined(); // 为null,不是undefined,通过  expect(n).not.toBeUndefined(); // 不为undefined,通过  expect(n).not.toBeTruthy(); // 不为真,通过  expect(n).toBeFalsy(); // 为假,通过});test('zero', () => {  const z = 0;  expect(z).not.toBeNull(); // 不为null,通过  expect(z).toBeDefined(); // 不是undefined,通过  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); //对于数字类型来讲,toBe和toEqual效果是相同的});

javascript中,浮点类型判断也许会有误差,如常知的0.1 + 0.2 != 0.3,这时候需要使用toBeCloseTo来进行匹配

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/);});

toMatch匹配器可以对字符串进行正则匹配

数组和可迭代对象

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');});

toContain匹配器可以判断一个数组或可迭代对象中是否包含某个特定项

抛出异常

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); // 匹配Error类型错误  // You can also use the exact error message or a regexp  expect(compileAndroidCode).toThrow('you are using the wrong JDK'); //匹配错误message  expect(compileAndroidCode).toThrow(/JDK/); // 可以使用正则匹配});

如果测试的特定的函数需要抛出一个错误,需要使用toThrow匹配器

测试异步代码

回调函数

test('the data is peanut butter', done => {  function callback(data) {    expect(data).toBe('peanut butter');    done(); // 标志回调函数执行完毕  }  fetchData(callback);});

test函数接收一个函数参数,执行函数表示回调函数执行完毕,测试结束,如果done()没有被调用执行,那么这个测试将会失败

Promise

更简单的方式是使用PromiseJest会等待Promise执行完毕,如果Promise执行reject,测试将失败

test('the data is peanut butter', () => {  return fetchData().then(data => {    expect(data).toBe('peanut butter');  });});

.resolves

可以用resolves匹配器来匹配Promiseresolve

test('the data is peanut butter', () => {  return expect(fetchData()).resolves.toBe('peanut butter');});

.rejects

如果想要Promisereject,可以使用.rejects匹配器

test('the fetch fails with an error', () => {  return expect(fetchData()).rejects.toMatch('error');});

async/await

最简单的方法是使用asyncawait

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.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');});

设置测试前置和后置任务

某些测试用例,需要在运行测试时进行一些准备工作,如类实例的初始化,测试结束后的某些操作

beforeEachafterEach每次测试设置

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在每一个测试用例开始测试之前执行,如上每次测试之前会执行initializeCityDatabase()

afterEach则是在每一个测试用例测试结束后执行,如上每次测试之后会执行clearCityDatabase()

beforeEachafterEach处理异步代码的方式和test一样,可以在异步代码执行完毕后调用done()函数,也可以返回一个PromiseJest处理

beforeAllafterAll一次性设置

某些情况,如异步初始化数据库,只需要在某一组(简单来讲可以是一个*.test.js文件)测试之前和结束时进行设置,可以使用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();});

作用域

默认情况下,beforeafter会以文件为作用域作用于每次都测试,你也可以使用describe来将测试分组,当beforeafter包含在describe块中时,它们就只作用于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 —— describe 中的测试依旧会执行最外层`before`// 2 - beforeEach// 2 - test// 2 - afterEach// 1 - afterEach// 2 - afterAll// 1 - afterAll

需要注意的是,最外层的beforeafter仍然会作用于describe块中的测试

describetest块的执行顺序

Jest会在所有真正的测试开始之前执行测试文件里所有的describe,所以任务写在before*after*而不是直接卸载describe中,当所有describe执行完毕后,Jest会按照test出现顺序依次执行

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