Jest单元测试实例

374 阅读6分钟

1.如何使用

Jest 可以通过 npm 或 yarn 进行安装。以 npm 为例,既可用 npm install -g jest 进行全局安装;也可以只局部安装、并在 package.json 中指定 test 脚本:

  "scripts": {
    "test": "jest"
  }
}

接下来的实例是使用Nuxt.js框架构建demo,在demo配置时选择的测试框架时Jest

Jest 的测试脚本名形如 *.test.js,不论 Jest 是全局运行还是通过 npm run test 运行,它都会执行当前目录下所有的*.test.js 或 *.spec.js 文件、完成测试。

2.简单实例

在根目录下创建utils/sum.js

function sum(a, b){
  return a + b;
}
export { sum };

test文件下添加sum.test.js

import { sum } from '~/utils/sum.js';

describe('test testObject', () => {
  test('测试求和函数', () => {
      expect(sum(2, 2)).toBe(4);
  });
})

describe() 函数表示一组用例

test() 函数表示一个测试用例

expect() 函数返回一个期望值对象,该对象提供了大量的工具方法来做结果判定

toBe() 函数表示断言方法

在Jest官方文档中,it 是 test 的别名,二者可以通用

最后运行 yarn test 或 npm run test,将打印出下面的消息,表示测试通过了。

image.png

也可以在编辑器VScode安装Jest插件,每次保存可以看到测试是否通过 image.png

3.匹配器

3.1判断相等(toBe&toEqual)

test('22等于4', () => {
    expect(2+2).toBe(4);
});
// 测试对象相等
test('测试对象的值', () => {
    const data = {a: 1};
    expect(data).toEqual({a: 1});
});
// 测试不等,相反的值
test('22不等于1', () => {
    expect(2 + 2).not.toBe(1);
});

3.2判断真假、空值

  • toBeNull 只匹配 null;
  • toBeUndefined 只匹配 undefined;
  • toBeDefined 与 toBeUndefined 相反;
  • toBeTruthy 匹配任何 if 语句为真;
  • toBeFalsy 匹配任何 if 语句为假;

image.png

3.3判断数字

  • toBeGreaterThan 匹配大于某值;
  • toBeGreaterThanOrEqual 匹配大于或等于某值;
  • toBeLessThan 匹配小于某值;
  • toBeLessThanOrEqual 匹配小于或等于某值;

image.png

3.4判断浮点数

  • toBeCloseTo 解决浮点数精度带来的问题

image.png

3.5判断字符串(toMatch

image.png

异步测试

4.1callback回调函数:done

如果你有一个fetchData(callback)的function用来获取数据,并且在获取完成的时候调用callback 函数,你想测试返回的数据是“peanut butter” ,默认情况下当fetchData执行完成的时候Jest的测试就完成了,这并不是你所期望的那样的去运行。

// Don't do this!
test('the data is peanut butter', () => {
  function callback(data) {
    expect(data).toBe('peanut butter');
  }

  fetchData(callback);
});

上面代码的问题就在于一旦fetchData完成,测试也就执行完成,然后再调用回调。

Jest提供了一种用于测试的实现方式,下面代码 done() 被执行则意味着callback函数被调用。

test('the data is peanut butter', done => {
  function callback(data) {
    expect(data).toBe('peanut butter');
    done();
  }

  fetchData(callback);
});

如果 done 永远都不被调用,那么的测试将会失败,这也正是我们期望的(我们希望callback被调用,并且返回的data是我们期望的值)

4.2 返回Promise

4.2.1assertions 断言

如果使用的是 promise,需要在测试中 返回 一个 promise,Jest 会自动等待 promise 被解析处理,如果 promise 被拒绝,那么测试失败。

test("Test async code with promise", () => {
  // 一个rejected状态的 Promise 不会让测试失败
  expect.assertions(1);
  return doAsync().then((data) => {
    expect(data).toBe('example');
  });
});

test("Test promise with an error", () => {
  // 一个fulfilled状态的 Promise 不会让测试失败
  expect.assertions(1);
  return doAsync().catch(e => {
    expect(e).toMatch('error')
  });
});

注意1:确保 返回promise,如果忽略掉 return,那么测试会在 promise 完成之前完成。

注意2:expect.assertions(number)验证在测试期间是否调用了一定数量的断言。

同时满足以上两个条件

函数 doAsync,该函数接收两个回调 callback1 和 callback2,它将以未知顺序异步调用这两个回调。

test("doAsync calls both callbacks", () => {
  expect.assertions(2);
  function callback1(data) {
    expect(data).toBeTruthy();
  }
  function callback2(data) {
    expect(data).toBeTruthy();
  }
  doAsync(callback1, callback2);
});

使用 expect.assertions(2) 确保两个回调都实际被调用。

4.2.2resolves / .rejects Jest语法糖

// 假设 doAsync() 返回一个promise,resolve的结果为字符串'example'
it('Test async code with promise', () => {
  expect.assertions(1);
  return expect(doAsync()).resolves.toBe('example');
});

it('Test promise with an error', () => {
  expect.assertions(1);
  return expect(doAsync()).rejects.toMatch('error');
});

4.2.3async/await Promise语法糖

// 假设 doAsync() 返回一个promise,resolve的结果为字符串'example'
it('Test async code with promise', async () => {
  expect.assertions(1);
  const data = await doAsync();
  expect(data).toBe('example');
});

async/await 也可以和 resolves/rejects 一起使用:

// 假设 doAsync() 返回一个promise,resolve的结果为字符串'example'
it('Test async code with promise', async () => {
  expect.assertions(1);
  await expect(doAsync()).resolves.toBe('example');
  });
});

4.3done 和 assertions 区别

done:异步回调确保测试

assertions:Promise确保测试

一般测试的时候,异步都是模拟 mock 出来的,要自己控制结束,而不是真正的异步。所以 expect.assertions 某些情况下无法替代 done

5.Mock函数

在项目中,一个模块的方法内常常会去调用另外一个模块的方法。在单元测试中,我们可能并不需要关心内部调用的方法的执行过程和结果,只想知道它是否被正确调用即可,甚至会指定该函数的返回值。此时,使用Mock函数是十分有必要。

5.1jest.fn()

jest.fn()是创建 Mock 函数最简单的方式,如果没有定义函数内部的实现,jest.fn() 会返回 undefined 作为返回值。

test('测试jest.fn()调用', () => {
  let mockFn = jest.fn();
  let result = mockFn(1, 2, 3);

  // 断言mockFn的执行后返回undefined
  expect(result).toBeUndefined();
  // 断言mockFn被调用
  expect(mockFn).toBeCalled();
  // 断言mockFn被调用了一次
  expect(mockFn).toBeCalledTimes(1);
  // 断言mockFn传入的参数为1, 2, 3
  expect(mockFn).toHaveBeenCalledWith(1, 2, 3);
})

5.2jest.mock()

通常情况下,我们需要调用api,发送ajax请求,从后台获取数据。但是我们在做前端测试的时候,并不需要去调用真实的接口,所以此时我们需要模拟 axios/fetch 模块,让它不必调用api也能测试我们的接口调用是否正确。

5.3jest.spyOn()

jest.spyOn() 方法创建一个mock函数,并且可以正常执行被spy的函数。 jest.spyOn() 是 jest.fn() 的语法糖,它创建了一个和被spy的函数具有相同内部代码的mock函数。

6.Jest钩子函数

6.1执行顺序和作用域

钩子函数是指在某一时刻,jest会自动调用的函数。如下:

  • beforeAll:在所有测试用例执行之前执行
  • afterAll:等所有测试用例都执行之后执行 ,可以在测试用例执行结束时,做一些处理
  • beforeEach:每个测试用例执行前执行,可让每个测试用例中使用的变量互不影响,因为分别为每个测试用例实例化了一个对象
  • afterEach:每个测试用例执行结束后,做一些处理

注意:钩子函数的作用域为: 所在的 describe 分组;

6.2describe中的基础代码执行顺序

6.3only和skip

test.only(name, fn)

describe.only(name, fn)

only 只对单个测试用例进行测试

test.skip(name, fn)

describe.skip(name, fn)

skip 跳过某个测试用例进行测试