Jest 备忘清单
Jest 是一款优雅、简洁的 JavaScript 测试框架,这里介绍了它的入门和 一些 API 的索引。
入门
介绍
Jest v29 是一款优雅、简洁的 JavaScript 测试框架。
无需配置大多数 JS 项目中即装即用,无需配置优秀接口从it到expect- Jest 将工具包整合在一处。文档齐全、不断维护,非常不错。隔离的并行进行测试,发挥每一丝算力。快照轻松编写持续追踪大型对象的测试,并在测试旁或代码内显示实时快照。代码覆盖无需其他操作,您仅需添加--coverage参数来生成代码覆盖率报告。
快速开始
npm install --save-dev jest
Add to package.json
"scripts": {"test": "jest"}
运行你的测试
npm test -- --watch
查看: Getting started
编写测试
describe('My work', () => {test('works', () => {expect(2).toEqual(2)})})
BDD 语法
``js {2}
describe('My work', () => {
it('works', () => { //it是test`的别名
···
})
})
### 测试结构<!--rehype:wrap-class=col-span-3 row-span-2-->```jsdescribe('makePoniesPink', () => {beforeAll(() => {/* 在所有测试之前运行 */})afterAll(() => {/* 在所有测试后运行 */})beforeEach(() => {/* 在每次测试之前运行 */})afterEach(() => {/* 每次测试后运行 */})test('make each pony pink', () => {const actual = fn(['Alice', 'Bob', 'Eve'])expect(actual).toEqual(['Pink Alice', 'Pink Bob', 'Pink Eve'])})})
聚焦测试
describe.only(···)it.only(···)// 别名: fit()
查看: test.only
可选参数
| Flag | Description |
|---|---|
--coverage |
查看测试覆盖率摘要 |
--detectOpenHandles |
查看未关闭端口的摘要 |
--runInBand |
一个接一个地运行所有测试 |
--bail,-b |
失败跳出测试 |
更多参数查看 Jest CLI Options
跳过测试
describe.skip(···)it.skip(···)// 别名: xit()
查看: test.skip
基本测试用例
expect(value).not.toBe(value).toEqual(value).toBeTruthy()// Errors 测试expect(value).toThrow(error).toThrowErrorMatchingSnapshot()
快照
expect(value).toMatchSnapshot().toMatchInlineSnapshot()
Errors
expect(value).toThrow(error).toThrowErrorMatchingSnapshot()
Objects
expect(value).toBeInstanceOf(Class).toMatchObject(object).toHaveProperty(keyPath, value)
Objects
expect(value).toContain(item).toContainEqual(item).toHaveLength(number)
Numbers
expect(value).toBeCloseTo(number, numDigits).toBeGreaterThan(number).toBeGreaterThanOrEqual(number).toBeLessThan(number).toBeLessThanOrEqual(number)
Booleans
expect(value).toBeFalsy().toBeNull().toBeTruthy().toBeUndefined().toBeDefined()
Strings
expect(value).toMatch(regexpOrString)
NaN
test('当值为 NaN 时通过', () => {expect(NaN).toBeNaN();expect(1).not.toBeNaN();});
Others
expect.extend(matchers)expect.any(constructor)expect.addSnapshotSerializer(serializer)expect.assertions(1)
匹配器
基本匹配器
expect(42).toBe(42) // 严格相等 (===)expect(42).not.toBe(3) // 严格相等 (!==)expect([1, 2]).toEqual([1, 2]) // 深度相等// 深度相等expect({ a: undefined, b: 2 }).toEqual({ b: 2 })// 严格相等 (Jest 23+)expect({ a: undefined, b: 2 }).not.toStrictEqual({ b: 2 })
真实性
// 匹配 if 语句视为 true 的任何内容// (not false、0、''、null、undefined、NaN)expect('foo').toBeTruthy()// 匹配 if 语句视为 false 的任何内容// (false、0、''、null、undefined、NaN)expect('').toBeFalsy()// 仅匹配 nullexpect(null).toBeNull()// 仅匹配未定义expect(undefined).toBeUndefined()// toBeUndefined 的反义词expect(7).toBeDefined()// 匹配真假expect(true).toEqual(expect.any(Boolean))
数字
// 大于expect(2).toBeGreaterThan(1)// 大于或等于expect(1).toBeGreaterThanOrEqual(1)// 小于expect(1).toBeLessThan(2)// 小于或等于expect(1).toBeLessThanOrEqual(1)// 接近于expect(0.2 + 0.1).toBeCloseTo(0.3, 5)// 原始值的传递类型expect(NaN).toEqual(expect.any(Number))
字符串
// 检查字符串是否与正则表达式匹配。expect('long string').toMatch('str')expect('string').toEqual(expect.any(String))expect('coffee').toMatch(/ff/)expect('pizza').not.toMatch('coffee')expect(['pizza', 'coffee']).toEqual([expect.stringContaining('zz'),expect.stringMatching(/ff/)])
数组
expect([]).toEqual(expect.any(Array))const exampleArray = ['Alice', 'Bob', 'Eve']expect(exampleArray).toHaveLength(3)expect(exampleArray).toContain('Alice')expect(exampleArray).toEqual(expect.arrayContaining(['Alice', 'Bob']))expect([{ a: 1 }, { a: 2 }]).toContainEqual({ a: 1 }) // 包含相等
对象
expect({ a:1 }).toHaveProperty('a')expect({ a:1 }).toHaveProperty('a', 1)expect({ a: {b:1} }).toHaveProperty('a.b')expect({ a:1, b:2 }).toMatchObject({a:1})expect({ a:1, b:2 }).toMatchObject({a: expect.any(Number),b: expect.any(Number),})expect([{ a: 1 }, { b: 2 }]).toEqual([expect.objectContaining({ a: expect.any(Number) }),expect.anything(),])
模拟函数
// const fn = jest.fn()// const fn = jest.fn().mockName('Unicorn') -- 命名为 mock, Jest 22+// 函数被调用expect(fn).toBeCalled()// 函数*未*调用expect(fn).not.toBeCalled()// 函数只被调用一次expect(fn).toHaveBeenCalledTimes(1)// 任何执行都带有这些参数expect(fn).toBeCalledWith(arg1, arg2)// 最后一个执行是用这些参数expect(fn).toHaveBeenLastCalledWith(arg1, arg2)// 第 N 个执行带有这些参数(Jest 23+)expect(fn).toHaveBeenNthCalledWith(callNumber, args)// 函数返回没有抛出错误(Jest 23+)expect(fn).toHaveReturnedTimes(2)// 函数返回一个值(Jest 23+)expect(fn).toHaveReturnedWith(value)// 最后一个函数调用返回一个值(Jest 23+)expect(fn).toHaveLastReturnedWith(value)// 第 N 个函数调用返回一个值(Jest 23+)expect(fn).toHaveNthReturnedWith(value)expect(fn.mock.calls).toEqual([['first', 'call', 'args'],['second', 'call', 'args'],]) // 多次调用// fn.mock.calls[0][0] — 第一次调用的第一个参数expect(fn.mock.calls[0][0]).toBe(2)
别名
toBeCalled→toHaveBeenCalledtoBeCalledWith→toHaveBeenCalledWithlastCalledWith→toHaveBeenLastCalledWithnthCalledWith→toHaveBeenNthCalledWithtoReturnTimes→toHaveReturnedTimestoReturnWith→toHaveReturnedWithlastReturnedWith→toHaveLastReturnedWithnthReturnedWith→toHaveNthReturnedWith
杂项
// 检查对象是否是类的实例。expect(new A()).toBeInstanceOf(A)// 检查对象是否是函数的实例。expect(() => {}).toEqual(expect.any(Function))// 匹配除 null 或 undefined 之外的任何内容expect('pizza').toEqual(expect.anything())
快照
// 这可确保某个值与最近的快照匹配。expect(node).toMatchSnapshot()// Jest 23+expect(user).toMatchSnapshot({date: expect.any(Date),})// 确保值与最近的快照匹配。expect(user).toMatchInlineSnapshot()
Promise 匹配器(Jest 20+)
test('resolve to lemon', () => {// 验证在测试期间是否调用了一定数量的断言。expect.assertions(1)// 确保添加return语句return expect(Promise.resolve('lemon')).resolves.toBe('lemon')return expect(Promise.reject('octopus')).rejects.toBeDefined()return expect(Promise.reject(Error('pizza'))).rejects.toThrow()})
或者使用 async/await:
test('resolve to lemon', async () => {expect.assertions(2)await expect(Promise.resolve('lemon')).resolves.toBe('lemon')await expect(Promise.resolve('lemon')).resolves.not.toBe('octopus')})
例外
// const fn = () => {// throw new Error('Out of cheese!')// }expect(fn).toThrow()expect(fn).toThrow('Out of cheese')// 测试错误消息在某处说“cheese”:这些是等价的expect(fn).toThrowError(/cheese/);expect(fn).toThrowError('cheese');// 测试准确的错误信息expect(fn).toThrowError(/^Out of cheese!$/);expect(fn).toThrowError(new Error('Out of cheese!'));// 测试函数在调用时是否抛出与最新快照匹配的错误。expect(fn).toThrowErrorMatchingSnapshot()
别名
toThrowError→toThrow
异步测试
实例
在异步测试中指定一些预期的断言是一个很好的做法,所以如果你的断言根本没有被调用,测试将会失败。
test('async test', () => {// 在测试期间恰好调用了三个断言expect.assertions(3)// 或者 - 在测试期间至少调用一个断言expect.hasAssertions()// 你的异步测试})
请注意,您也可以在任何 describe 和 test 之外对每个文件执行此操作:
beforeEach(expect.hasAssertions)
这将验证每个测试用例至少存在一个断言。 它还可以与更具体的 expect.assertions(3) 声明配合使用。
请参阅 Jest 文档中的 更多示例
async/await
test('async test', async () => {expect.assertions(1)const result = await runAsyncOperation()expect(result).toBe(true)})
done() 回调
test('async test', (done) => {expect.assertions(1)runAsyncOperation()setTimeout(() => {try {const res = getAsyncOperatResult()expect(res).toBe(true)done()} catch (err) {done.fail(err)}})})
将断言包装在 try/catch 块中,否则 Jest 将忽略失败
Promises
test('async test', () => {expect.assertions(1)return runAsyncOperation().then((result) => {expect(result).toBe(true)})})
从你的测试中 返回 一个 Promise
模拟
模拟函数
test('call the callback', () => {const callback = jest.fn()fn(callback)expect(callback).toBeCalled()expect(callback.mock.calls[0][1].baz).toBe('pizza') // 第一次调用的第二个参数// 匹配第一个和最后一个参数,但忽略第二个参数expect(callback).toHaveBeenLastCalledWith('meal',expect.anything(),'margarita')})
您还可以使用快照:
test('call the callback', () => {// mockName 在 Jest 22+ 中可用const callback = jest.fn().mockName('Unicorn')fn(callback)expect(callback).toMatchSnapshot()// ->// [MockFunction Unicorn] {// "calls": Array [// ...})
并将实现传递给 jest.fn 函数:
const callback = jest.fn(() => true)
返回、解析和拒绝值
您的模拟可以返回值:
const callback= jest.fn().mockReturnValue(true)const callbackOnce= jest.fn().mockReturnValueOnce(true)
或解析值:
const promise= jest.fn().mockResolvedValue(true)const promiseOnce= jest.fn().mockResolvedValueOnce(true)
他们甚至可以拒绝值:
const failedPromise =jest.fn().mockRejectedValue('Error')const failedPromiseOnce =jest.fn().mockRejectedValueOnce('Error')
你甚至可以结合这些:
const callback = jest.fn().mockReturnValueOnce(false).mockReturnValue(true)// ->// call 1: false// call 2+: true
使用 jest.mock 方法模拟模块
// 原来的 lodash/memoize 应该存在jest.mock('lodash/memoize',() => (a) => a)// 不需要原始的 lodash/memoizejest.mock('lodash/memoize',() => (a) => a,{ virtual: true })
注意:当使用 babel-jest 时,对 jest.mock 的调用将自动提升到代码块的顶部。 如果您想明确避免这种行为,请使用 jest.doMock。
使用模拟文件模拟模块
创建一个类似 __mocks__/lodash/memoize.js 的文件:
module.exports = (a) => a
添加到您的测试中:
jest.mock('lodash/memoize')
注意:当使用 babel-jest 时,对 jest.mock 的调用将自动提升到代码块的顶部。 如果您想明确避免这种行为,请使用 jest.doMock。手动模拟文档
模拟 getters 和 setters
const getTitle = jest.fn(() => 'pizza')const setTitle = jest.fn()const location = {}Object.defineProperty(location, 'title', {get: getTitle,set: setTitle,})
模拟 getter 和 setter (Jest 22.1.0+)
const location = {}const getTitle = jest.spyOn(location, 'title', 'get').mockImplementation(() => 'pizza')const setTitle = jest.spyOn(location, 'title', 'set').mockImplementation(() => {})
定时器模拟
为使用本机计时器函数(setTimeout、setInterval、clearTimeout、clearInterval)的代码编写同步测试。
// 启用假计时器jest.useFakeTimers()test('kill the time', () => {const callback = jest.fn()// 运行使用 setTimeout或setInterval 的代码const actual= someFunctionThatUseTimers(callback)// 快进直到所有定时器都执行完毕jest.runAllTimers()// 同步检查结果expect(callback).toHaveBeenCalledTimes(1)})
或者使用 advanceTimersByTime() 按时间调整计时器:
// 启用假计时器jest.useFakeTimers()test('kill the time', () => {const callback = jest.fn()// 运行使用 setTimeout或setInterval 的代码const actual= someFunctionThatUseTimers(callback)// 快进 250 毫秒jest.advanceTimersByTime(250)// 同步检查结果expect(callback).toHaveBeenCalledTimes(1)})
对于特殊情况,请使用 jest.runOnlyPendingTimers()。
注意: 您应该在测试用例中调用 jest.useFakeTimers() 以使用其他假计时器方法。
模拟对象方法
const spy = jest.spyOn(console, 'log').mockImplementation(() => {})expect(console.log.mock.calls).toEqual([['dope'], ['nope']])spy.mockRestore()
const spy = jest.spyOn(ajax, 'request').mockImplementation(() => Promise.resolve({success: true}))expect(spy).toHaveBeenCalled()spy.mockRestore()
清除和恢复模拟
对于一个模拟
// 清除模拟使用日期// (fn.mock.calls、fn.mock.instances)fn.mockClear()// 清除并删除任何模拟的返回值或实现fn.mockReset()// 重置并恢复初始实现fn.mockRestore()
注意:
mockRestore仅适用于由jest.spyOn创建的模拟。对于所有模拟:
// 清除所有 mock 的// mock.calls、mock.instances、// mock.contexts 和 mock.results 属性。jest.clearAllMocks()// 重置所有模拟的状态。jest.resetAllMocks()// 将所有模拟恢复到其原始值。jest.restoreAllMocks()
使用模拟时访问原始模块
jest.mock('fs')// 模拟模块const fs = require('fs')// 原始模块const fs = require.requireActual('fs')
数据驱动测试(Jest 23+)
使用不同的数据运行相同的测试
test.each([[1, 1, 2],[1, 2, 3],[2, 1, 3],])('.add(%s, %s)', (a, b, expected) => {expect(a + b).toBe(expected)})
使用模板文字相同
test.each`a | b | expected${1} | ${1} | ${2}${1} | ${2} | ${3}${2} | ${1} | ${3}`('returns $expected when $a is added $b', ({ a, b, expected }) => {expect(a + b).toBe(expected)})
或在“describe”级别
describe.each([['mobile'], ['tablet'], ['desktop']])('checkout flow on %s', (viewport) => {test('displays success page', () => {//})})
describe.each() 文档、test.each() 文档
跳过测试
不要运行这些测试
describe.skip('makePoniesPink'...tests.skip('make each pony pink'...
仅运行以下测试
describe.only('makePoniesPink'...tests.only('make each pony pink'...
测试有副作用的模块
实例
const modulePath = '../module-to-test'afterEach(() => {jest.resetModules()})test('第一次测试', () => {// 准备第一次测试的条件const result = require(modulePath)expect(result).toMatchSnapshot()})test('第二个文本', () => {// 准备第二次测试的条件const fn = () => require(modulePath)expect(fn).toThrow()})
Node.js 和 Jest 会缓存你需要的模块。 要测试具有副作用的模块,您需要在测试之间重置模块注册表
另见
- Jest cheat sheet (github.com)
