Jest的介绍与使用

发布于: 6/12/2022 阅读大约需要7分钟

Jest /dʒɛst/ 是一款优雅、简洁的 JavaScript 测试框架。

支持 BabelTypeScriptNodeReactAngularVue 等诸多框架!

总结

让我们先来看一下总结, 如果需要细致阅读的话可以带着阅读总结时的疑问进行阅读

安装

npm install -D jest or yarn add -D jest

TypeScript下安装使用ts-jest(npm install -D ts-jest)

然后在jest.config.[ts,js]文件中设置preset: 'ts-jest'

API

Jest的API都可以直接使用, 无需import (当然, 非要import也不会报错)

Glboals API - 全局API

API功能主要分为三类(仅依照本人意愿, 不代表官方意见)

  • 生命周期钩子类
    • beforeAll
    • afterAll
    • beforeEach
    • afaterEach
  • 声明测试组类(块) - describe
  • 定义测试用例类 - test, 别名为it, 有.todo方法

除钩子函数外, 定义方法按照通用功能分为

  • basic: 方法基础体
  • .skip: 注释方法, 用于跳过该方法, 别名前缀为x
  • .only: 唯一方法, 用于同文件下仅执行该方法, 别名前缀为f
  • .each(table): 循环方法

在保持**.only****.skip**不共存的前提下, Jest的API形式基本上保持

基础方法体 + [.each, .only, .skip]中的一个或两个

比如it.skip(name, fn), it.skip.each(name, fn)

详细API列表以及功能可以查看API说明

Expect API - 期望API

用于写测试用例时做值匹配而提供的一些方法, 由于方法名起的都比较直观, 就不列举以及特殊说明了.

下面为官网的API列表, 哪里不懂点哪里吧

  • [expect(value)](#expectvalue)
  • [expect.extend(matchers)](#expectextendmatchers)
  • [expect.anything()](#expectanything)
  • [expect.any(constructor)](#expectanyconstructor)
  • [expect.arrayContaining(array)](#expectarraycontainingarray)
  • [expect.assertions(number)](#expectassertionsnumber)
  • [expect.hasAssertions()](#expecthasassertions)
  • [expect.not.arrayContaining(array)](#expectnotarraycontainingarray)
  • [expect.not.objectContaining(object)](#expectnotobjectcontainingobject)
  • [expect.not.stringContaining(string)](#expectnotstringcontainingstring)
  • [expect.not.stringMatching(string | regexp)](#expectnotstringmatchingstring--regexp)
  • [expect.objectContaining(object)](#expectobjectcontainingobject)
  • [expect.stringContaining(string)](#expectstringcontainingstring)
  • [expect.stringMatching(string | regexp)](#expectstringmatchingstring--regexp)
  • [expect.addSnapshotSerializer(serializer)](#expectaddsnapshotserializerserializer)
  • [.not](#not)
  • [.resolves](#resolves)
  • [.rejects](#rejects)
  • [.toBe(value)](#tobevalue)
  • [.toHaveBeenCalled()](#tohavebeencalled)
  • [.toHaveBeenCalledTimes(number)](#tohavebeencalledtimesnumber)
  • [.toHaveBeenCalledWith(arg1, arg2, ...)](#tohavebeencalledwitharg1-arg2-)
  • [.toHaveBeenLastCalledWith(arg1, arg2, ...)](#tohavebeenlastcalledwitharg1-arg2-)
  • [.toHaveBeenNthCalledWith(nthCall, arg1, arg2, ....)](#tohavebeennthcalledwithnthcall-arg1-arg2-)
  • [.toHaveReturned()](#tohavereturned)
  • [.toHaveReturnedTimes(number)](#tohavereturnedtimesnumber)
  • [.toHaveReturnedWith(value)](#tohavereturnedwithvalue)
  • [.toHaveLastReturnedWith(value)](#tohavelastreturnedwithvalue)
  • [.toHaveNthReturnedWith(nthCall, value)](#tohaventhreturnedwithnthcall-value)
  • [.toHaveLength(number)](#tohavelengthnumber)
  • [.toHaveProperty(keyPath, value?)](#tohavepropertykeypath-value)
  • [.toBeCloseTo(number, numDigits?)](#tobeclosetonumber-numdigits)
  • [.toBeDefined()](#tobedefined)
  • [.toBeFalsy()](#tobefalsy)
  • [.toBeGreaterThan(number | bigint)](#tobegreaterthannumber--bigint)
  • [.toBeGreaterThanOrEqual(number | bigint)](#tobegreaterthanorequalnumber--bigint)
  • [.toBeLessThan(number | bigint)](#tobelessthannumber--bigint)
  • [.toBeLessThanOrEqual(number | bigint)](#tobelessthanorequalnumber--bigint)
  • [.toBeInstanceOf(Class)](#tobeinstanceofclass)
  • [.toBeNull()](#tobenull)
  • [.toBeTruthy()](#tobetruthy)
  • [.toBeUndefined()](#tobeundefined)
  • [.toBeNaN()](#tobenan)
  • [.toContain(item)](#tocontainitem)
  • [.toContainEqual(item)](#tocontainequalitem)
  • [.toEqual(value)](#toequalvalue)
  • [.toMatch(regexp | string)](#tomatchregexp--string)
  • [.toMatchObject(object)](#tomatchobjectobject)
  • [.toMatchSnapshot(propertyMatchers?, hint?)](#tomatchsnapshotpropertymatchers-hint)
  • [.toMatchInlineSnapshot(propertyMatchers?, inlineSnapshot)](#tomatchinlinesnapshotpropertymatchers-inlinesnapshot)
  • [.toStrictEqual(value)](#tostrictequalvalue)
  • [.toThrow(error?)](#tothrowerror)
  • [.toThrowErrorMatchingSnapshot(hint?)](#tothrowerrormatchingsnapshothint)
  • [.toThrowErrorMatchingInlineSnapshot(inlineSnapshot)](#tothrowerrormatchinginlinesnapshotinlinesnapshot)

Mock Functions API - 模拟方法API

Mock方法主要用于模拟一些jsDom还未收录的或者当前环境没有的对象或者方法

比如 fetch 方法

报告指标

在我们执行测试命令之后命令行会输出测试报告表格

--------------------|---------|----------|---------|---------|---------------------------------
File                | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s                                                          
--------------------|---------|----------|---------|---------|---------------------------------
All files           |    48.2 |      100 |   10.52 |    48.2 |                                                                            
...

测试覆盖率指标含义如下

  • Stmts: Statement coverage, 声明覆盖率(是否所有的声明都被使用)
  • Branch: branch coverage, 分支覆盖率(是否所有的判断都执行了)
  • Funcs: Function coverage, 方法覆盖率(是否所有方法都被调用)
  • Lines: Line coverage, 代码行覆盖率(是否所有代码都执行了)
  • Uncovered Line: 未覆盖到的行号

基础使用

安装

yarn add -D jest
npm install -D jest

有关NPM的使用可以查看npm的使用

初始化项目配置文件

jest --init

需要全局安装jest npm install -g jest

$ jest --init

The following questions will help Jest to create a suitable configuration for your project
✔ Would you like to use Typescript for the configuration file? … yes
✔ Choose the test environment that will be used for testing › jsdom (browser-like)
✔ Do you want Jest to add coverage reports? … yes
✔ Which provider should be used to instrument code for coverage? › v8
✔ Automatically clear mock calls and instances between every test? … no

随后将会在项目根目录下生成jest.config.ts(由于第一个问题选yes所以是ts文件)

TypeScript支持

可以使用[ts-jest](https://kulshekhar.github.io/ts-jest), 它提供了类型校验

npm install --save-dev ts-node jest typescript ts-jest @types/jest

然后在jest.config.ts中添加preset: 'ts-jest'

export default {
  // ...
  preset: 'ts-jest',
  // ...
}

有关Babel的相关使用说明可以参考

配置

TODO:

API说明

本段内容基本属于翻译官网, 有英文基础的可以移步官网直接查看

生命周期钩子

这里笔者在方法参数内写了默认值, 即表明该参数为选填参数, 同时告知默认值

beforeAll(fn, timeout = 5)

在所有用例执行之前, 通常用于配置一些全局设置或状态

  • fn: 回调方法
  • timeout: 非必须参数, 超时时间, 默认5s

beforeEach(fn, timeout = 5)

在每个用例执行之前, 通常用于配置每个测试用例创建的一些临时状态

afterAll(fn, timeout = 5)

全部用例(test)执行完之后执行, 通常用于清理全局设置及状态

afterEach(fn, timeout = 5)

每个用例执行完之后执行, 通常用于清理每个测试用例创建的一些临时状态

声明测试组 - Describe

describe(name, fn)

创建一个测试组, 里面包含多个测试用例(test). 测试组可以嵌套声明.

  • name: 测试组名称
  • fn: 测试组方法

describe.each(table)(name, fn, timeout = 5)

为测试组内的每个测试用例提供相同的测试套件(参数集合)

  • table: 测试数据集合
  • name: 测试组名称, 支持formatting(如%p, %s等)

会循环测试数据集合, 将每一项作为参数传给测试用例, 如果参数为1维数组, 则每一项将会被拆为单独的数组后组合

[1,2,3] —> [[1], [2], [3]]

可以理解为将一维数组作为...args, 伪代码理解如下

table.forEach(item => {
  if (item instanceof Array) {
	  fn(...item)
  } else {
    fn(item)
  }
})

例如

describe.each([
  [1, 1, 2],
  [1, 2, 3],
  [2, 1, 3],
])('.add(%i, %i)', (a, b, expected) => {
  test(`returns ${expected}`, () => {
    expect(a + b).toBe(expected);
  });

  test(`returned value not be greater than ${expected}`, () => {
    expect(a + b).not.toBeGreaterThan(expected);
  });

  test(`returned value not be less than ${expected}`, () => {
    expect(a + b).not.toBeLessThan(expected);
  });
});
/**
.add(1, 1)
  ✓ returns 2 (2 ms)
  ✓ returned value not be greater than 2
  ✓ returned value not be less than 2 (1 ms)
.add(1, 2)
  ✓ returns 3
  ✓ returned value not be greater than 3 (1 ms)
  ✓ returned value not be less than 3
.add(2, 1)
  ✓ returns 3
  ✓ returned value not be greater than 3 (1 ms)
  ✓ returned value not be less than 3
*/
describe.each([
  {a: 1, b: 1, expected: 2},
  {a: 1, b: 2, expected: 3},
  {a: 2, b: 1, expected: 3},
])('.add($a, $b)', ({a, b, expected}) => {
  test(`returns ${expected}`, () => {
    expect(a + b).toBe(expected);
  });

  test(`returned value not be greater than ${expected}`, () => {
    expect(a + b).not.toBeGreaterThan(expected);
  });

  test(`returned value not be less than ${expected}`, () => {
    expect(a + b).not.toBeLessThan(expected);
  });
});

该方法也支持describe.eachtable`(name, fn, timeout)“的形式, 比较难用, 如有兴趣请前往官网查看

describe.only(name, fn)

在一个文件中有多个describe块时, 只会执行第一个only

该API有别名, 为fdescribe(name, fn)

describe.only.each(table)(name, fn)

只会执行文件内该测试组内的测试用例

注释测试组 - skip

describe.skip(name, fn)

用于注释掉测试组, 在普通的声明后面直接加上skip, 方便注释

describe.skip.each(table)(name, fn)

注释each测试组

测试用例 -test

测试用例API关键词为 test, 方法别名为 it

test(name, fn, timeout = 5)

定义测试用例

也可以使用it(name, fn, timeout)

  • name: 描述测试内容
  • fn: 需要执行的方法, 里面包含了该测试用例通过的条件
  • timeout: 测试超时时间(依旧是5秒)

test.concurrent(name, fn, timeout = 5)

同时执行name相同的测试用例, 比如

test.concurrent('addition of 2 numbers', async () => {
  expect(5 + 3).toBe(8);
});

test.concurrent('subtraction 2 numbers', async () => {
  expect(5 - 3).toBe(2);
});

test.concurrent.each(table)(name, fn, timeout = 5)

test.concurrent的循环版, 同步循环用例

test.concurrent.only.each(table)(name, fn, timeout = 5)

仅执行该同步循环用例

test.concurrent.skip.each(table)(name, fn, timeout = 5)

跳过该同步循环用例

test.each(table)(name, fn, timeout = 5)

循环用例

test.only(name, fn, timeout = 5)

同文件下仅执行该用例

test.only.each(table)(name, fn, timeout = 5)

同文件下仅执行该循环用例

test.skip(name, fn)

跳过该用例

it.skip('skip', () => {})

test.skip.each(table)(name, fn)

跳过循环用例

test.todo(description)

类似于TODO的注释, 但是会在执行测试文件的时候输出.

如果文件内有it.only类的用例, 则todo也将会被跳过

it.todo('这是TODO')
// 将会输出
// ✎ todo 这是TODO