Jest入门文档

5,689 阅读6分钟

前端测试 Jest (未完待续)

1 配置环境

  1. node npm install --save-dev jest 需在环境变量中配置
  2. yarn yarn add --dev jest yarn global add jest

2 demo

jest 的工程文件后缀都是xx.test.js,我们在运行一个jest工程的时候,jest会自动查找运行的后缀为*.test.js的脚本进行单元测试。

machers 众多的匹配判断器

  1. toBe() 用于检验基本数据类型的值是否相等
  2. toEqual() 用于检验引用数据类型的值,由于js本身object数据类型的本身特性,引用数据类型对比只是指针的对比,但是需要对比对象的每个值,所以这时候用到的是toEqual()
  3. Truthiness 布尔值判断的匹配器
  • toBeNull 只匹配 null
  • toBeUndefined 只匹配 undefined
  • toBeDefined 与 toBeUndefined 相反
  • toBeTruthy 匹配任何 if 语句为真
  • toBeFalsy 匹配任何 if 语句为假
  1. 数字匹配器 用于判断数字值之间的对比
  • toBeGreaterThan 大于匹配器
  • toBeGreaterThanOrEqual 大于等于匹配器
  • toBeLessThan 小于匹配器
  • toBeLessThanOrEqual 小于等于匹配器
  • tobe 和 toequal 都是等价功能相同的对于数字
  1. toMatch 字符串匹配器 和字符串的match相同
  2. toContain 数组匹配器 用于判断数组中是否包含某些值
  3. toThrow 报错匹配器 用于测试特定的抛出错误,可以判断报错语句的文字(支持正则匹配),也可以判断报错类型。

异步代码的判断

js 基于单线程异步的特性,在代码中充斥的异步程序和各种回调,所以我们在测试异步代码的时候要格外注意。一下是各种异步的案例

  1. 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()永远不会调用,这个测试将失败,这也是你所希望发生的。
  1. 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'));
});
  1. .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');
    });
  1. 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');
});