Jest 实践

Jest 作为单元测试框架提供整个测试环境,包括except,assert语法,mock模块,代码覆盖率的能力。

1. 安装

yarn add --dev jest //yarn 
npm install --save-dev jest //npm

安装之后,根目录下运行 jest test,就会开始跑单元测试,默认会匹配以下路劲的文件[**/__tests__/**/*.[jt]s?(x), **/?(*.)+(spec|test).[tj]s?(x)]。通常情况下会将单元测试文件放到__test__目录下。

2. 生成基础配置文件

Jest通过命令行的方式生成一个配置文件jest.config.js,用来控制Jest的行为。

jest --init

执行该命令时会问以下几个问题:

问题 选项 说明 备注
Automatically clear mock calls and instances between every test? yes/no 是否在每个单元测试前自动清除模拟调用 通常选yes
Choose the test environment that will be used for testing jsdom(browser-like) or node 选择运行环境 如果涉及浏览器相关的操作,选择jsdom
Do you want Jest to add coverage reports? yes/no 是否生成测试覆盖率报告 通常选yes,可以生成覆盖率报告
Which provider should be used to instrument code for coverage? V8 or babel
Would you like to use Jest when running "test" script in "package.json"? yes/no 是否在package.json的script添加test命令 选择yes后,直接运行npm run test 就可以跑单元测试
Would you like to use Typescript for the configuration file? yes/no 配置文件是否用ts格式 项目中不用ts, 选no即可

常用配置

module.exports = {

 //是否将导入的模块自动mock,通常不要,因为有些mock逻辑是你可能想自定义
  automock: false,

  //是否在每次测试都清楚mock, 通常选true
  clearMocks: true,

//哪些文件要收集单测报告,通常根据文件自定义
  collectCoverageFrom: ['src/components/**/*.{js,jsx,ts,tsx}', 'src/hooks/**/*.{js,jsx,ts,tsx}', 'src/utils/**/*.{js,jsx,ts,tsx}'],

 //覆盖率报告的文件名称,默认coverage就好
  coverageDirectory: "coverage",
};

更多配置查看:https://jestjs.io/zh-Hans/docs/configuration
查看更多命令,执行jest help

3. 添加测试相关命令

package.json 中 script添加单元测试相关命令

"script": {
    "test": "jest test --coverage", 
    "test:update": "jest test --updateSnapshot",
    "test:watch": "jest test  --watch",
     "test:verbose": "jest test  --verbose",
}

注:无cross-env

"script": {
    "test": "cross-env BABEL_ENV=test jest --coverage",
    "test:update": "cross-env BABEL_ENV=test jest --updateSnapshot",
    "test:watch": "cross-env BABEL_ENV=test jest  --watch"
    "test:verbose": "cross-env BABEL_ENV=test jest  --verbose"
}

注:前提安装了cross-env
--coverage 执行之后可以在命令行中生成覆盖率报告以及在根目录下生成coverage文件。

屏幕快照 2021-08-06 上午11.33.46.png

关于覆盖率的知识:http://www.ruanyifeng.com/blog/2015/06/istanbul.html
浏览器打开coverage/lcov-report/index.html,可以看到每个文件的具体覆盖率,点开某个个文件,对于没有覆盖的地方,会用不同的颜色标记出来。
屏幕快照 2021-08-06 下午2.14.01.png

--updateSnapshot,更新快照,当快照发生变化时,确认快照需要变化,可以运行该命令。
--watch 观察者模式,每次文件变更会自动触发重新跑单元测试,通常在开发过程中使用,方便实时查看。
--verbose,层次显示测试套件中每个测试的结果,方便查看每个用例具体是什么。
屏幕快照 2022-03-17 下午2.27.15.png

更多的命令查看:https://jestjs.io/zh-Hans/docs/cli

4.使用Babel

需要测试ECMAScript2015+ 的代码,需要安装@babel/preset-env

yarn add --dev babel-jest @babel/core @babel/preset-env //yarn
npm install --save-dev babel-jest @babel/core @babel/preset-env  //npm

在工程的根目录下创建一个babel.config.js文件用于配置与你当前Node版本兼容的Babel

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

5. 一些基本用法

  1. 基本概念
    describe(name, fn)
    describe块称为"测试套件"(test suite),表示一组相关的测试。它是一个函数,第一个参数是测试套件的名称("加法函数的测试"),第二个参数是一个实际执行的函数。
    test(name, fn, timeout)
    其别名为(name, fn, timeout)。test块称为"测试用例"(test case),表示一个单独的测试,是测试的最小单位。它也是一个函数,第一个参数是测试用例的名称("1 加 1 应该等于 2"),第二个参数是一个实际执行的函数。
//跳过该测试用例
test.skip(name, fn)
//该测试用例标记为将要做的
test.todo(name, fn)
//只运行该测试用例
test.only(name, fn)
//__test__/sum.js
const sum = (a, b) => {
    return a+b
}
export default sum
//sum.test.js
import sum from '../sum'
describe('加法函数的测试', () => {
  test('1 加 1 应该等于 2', () => {
    expect(sum(1,1)).toBe(2);
  });

  test('1+1不等于3', () => {
    expect(sum(1,1)).not.toBe(3);
  });
});
  1. 钩子函数
beforeEach(fn, timeout)//每个测试用例执行前执行
afterEach(fn, timeout)//每个测试用例执行后执行
beforeAll(fn, timeout)//所有测试用例测试之前执行
afterAll(fn, timeout)//所有测试用例测试之后执行

更多全局函数参考:https://jestjs.io/zh-Hans/docs/api

  1. 断言
    expect函数+ matcher函数断言
    3.1 expect函数
expect(value) //后边可以跟匹配器
expect.anything() //匹配除了null和undefined的所有,可以检查是否使用非空参数调用模拟函数
expect.any(constructor) //匹配任何构造器
expect.objectContaining(object)
expect.not.objectContaining(object)
expect.arrayContaining(array) //匹配一个数组包含
expect.not.arrayContaining(array)
expect.stringContaining(string)
expect.not.stringContaining(string)
expect.extend(matchers) //自定义匹配器,想要了解jest匹配器实现的可以看这一部分
expect.assertions(number) //验证断言次数
expect.hasAssertions() //验证至少有一个断言

注:按日常使用频率排序
expect.any-expect.not.stringContaining 这几个通常搭配toBeCalledWith一起使用

//objectContaining常可以用来校验函数的入参
test('onPress gets called with the right thing', () => {
  const onPress = jest.fn();
  simulatePresses(onPress);
  expect(onPress).toBeCalledWith(
    expect.objectContaining({
      x: expect.any(Number),
      y: expect.any(Number),
    }),
  );
});

3.2 常用匹配器(Matcher)

toBe(value)//匹配确定值
toContain(item)//匹配数组的某一个
toEqual(value) //深度匹配
toHaveLength(number)//匹配长度
 
toBeCalled() //匹配函数被调用
toHaveBeenCalledTimes(number) //匹配函数调用次数
toHaveBeenCalledWith(arg1, arg2, ...)//匹配函数调用的参数
toHaveBeenLastCalledWith(arg1, arg2, ...)//匹配最后一次调用的返回值
 
toBeFalsy()//匹配假
toBeTruthy()//匹配真
toBeNull()//匹配空
toBeUndefined()//匹配Undefined

toThrow(error?) //匹配错误

toBeGreaterThan(number | bigint) //匹配大于
toBeGreaterThanOrEqual(number | bigint) //匹配大于等于
toBeLessThan(number | bigint) //匹配小于
toBeLessThanOrEqual(number | bigint) //匹配小于等于
 
toMatchSnapshot(propertyMatchers?, hint?) 生成快照文件,
toMatchInlineSnapshot(propertyMatchers?, inlineSnapshot) 行内匹配快照
 
not //取反

更多断言查阅:https://jestjs.io/zh-Hans/docs/expect
关于快照的使用,可以查看:https://jestjs.io/zh-Hans/docs/snapshot-testing

  1. mock函数
    函数和模块是我们项目中重要的组成部分,在我们对一个函数或模块的测试时,可能依赖其他的函数和模块,为了避免干扰,我们需要把影响我们测试的其它变成变成固定不变的,因此需要mock。
//mock一个函数
jest.fn()
 
const mockFn = jest.fn()
mockFn.mockClear()//清除mock
mockFn.mockImplementation(fn) //mock函数实现
mockFn.mockImplementationOnce(fn)
mockFn.mockReturnValue(value)//mock返回值
mockFn.mockReturnValueOnce(value)
mockFn.mockResolvedValue(value) //mock异步函数Resolved时返回值
mockFn.mockResolvedValueOnce(value)
mockFn.mockRejectedValue(value) //mock异步函数Rejected时返回值
mockFn.mockRejectedValueOnce(value)
 
#mock 模块
jest.mock(...) //模块路径

更多:https://jestjs.io/zh-Hans/docs/mock-function-api
关于异步的测试,查看:https://jestjs.io/zh-Hans/docs/tutorial-async

  1. mock定时器
    如果被测试的文件包含定时器的功能,我们总不能等待指定的之间之后再去执行一些东西,因此我们需要对定时器相关的东西做些操作,已加快执行,消除等待时间。
jest.useFakeTimers() //使用mock的定时器,通常在beforeEach中调用
jest.useRealTimers() //使用真实的定时器,通常在afterEach中调用
jest.runAllTimers() //运行所有的定时器,加速定时器的运行
jest.clearAllTimers() //清除当前所有挂载的定时器
jest.runOnlyPendingTimers() //执行当前挂载的定时器
jest.advanceTimersByTime(ms) //提前XXms执行定时器

更多例子查看:https://jestjs.io/zh-Hans/docs/timer-mocks


好啦,Jest的基本知识到这里就结束了,如果想要了解更多的知识,还是需要去查Jest官网,亦或者想更深入得了解,则是去查看源码。如果不涉及DOM的操作,那么单元测试的基本东西可以到此结束,但是前端通常都是需要和DOM打交道的,接下来来就来讲讲怎样测试DOM, react-testing-libraryenzyme究竟怎么选。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容