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文件。
关于覆盖率的知识:http://www.ruanyifeng.com/blog/2015/06/istanbul.html
浏览器打开coverage/lcov-report/index.html,可以看到每个文件的具体覆盖率,点开某个个文件,对于没有覆盖的地方,会用不同的颜色标记出来。
--updateSnapshot
,更新快照,当快照发生变化时,确认快照需要变化,可以运行该命令。--watch
观察者模式,每次文件变更会自动触发重新跑单元测试,通常在开发过程中使用,方便实时查看。--verbose
,层次显示测试套件中每个测试的结果,方便查看每个用例具体是什么。更多的命令查看: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. 一些基本用法
- 基本概念
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);
});
});
- 钩子函数
beforeEach(fn, timeout)//每个测试用例执行前执行
afterEach(fn, timeout)//每个测试用例执行后执行
beforeAll(fn, timeout)//所有测试用例测试之前执行
afterAll(fn, timeout)//所有测试用例测试之后执行
更多全局函数参考:https://jestjs.io/zh-Hans/docs/api
- 断言
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
- 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
- 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-library和enzyme究竟怎么选。