知食记
搜索文档…
Jest 内部原理剖析
Jest 是 Facebook 开源的一个 JavaScript 测试框架,已经应用在 Babel / TypeScript / Node / React / Angular / Vue 等项目中。
在愉悦地编写 Jest 测试代码后,你是否会好奇 test / expect / mock / spyOn 这些内置方法是如何实现的呢?下面我们一起来看一下。

Test

Jest 中的 test 函数标准实例是:
1
test('Passing test', () => {})
2
// ✓ Passing test
Copied!
1
test('Failing test', () => new Error('Error message'))
2
// ✕ Failing test
3
// Error message
Copied!
这里可以注意到,如果第二个参数的函数执行没有抛出异常的话,会打印第一个参数,因此我们的模拟实现可以是:
1
function test(title, callback) {
2
try {
3
callback()
4
console.log(`${title}`)
5
} catch (error) {
6
console.error(`${title}`)
7
console.error(error)
8
}
9
}
Copied!

Expect

expect 函数的典型使用实例是:
1
// 需要测试的函数
2
function multiply(a, b) {
3
return a * b
4
}
5
6
// 测试用例
7
test('Multipling 3 by 4 is 12', () => {
8
expect(multiply(3, 4)).toBe(12)
9
} // ✓ Multipling 3 by 4 is 12
10
11
test('Multipling 3 by 4 is 12', () => {
12
expect(multiply(3, 4)).toBe(13)
13
}) // ✕ Multipling 3 by 4 is 12
14
// Expected: 13
15
// Received: 12
Copied!
注意到expect 的返回值具备一个可以调用的方法toBe ,那么我们可以这样模拟expect
1
function expect(current) {
2
return {
3
toBe(expected) {
4
if (current !== expected) {
5
throw new Error(`
6
Expected: ${expected}
7
Received: ${current}
8
`)
9
}
10
}
11
}
12
}
Copied!

Mock

当我们要避免测试一个功能时又依赖于不稳定的另外一个功能,我们创建一个 Mock 来更好的管理。Jest 使用 jest.fn 是实现 Mock
1
// random.js
2
function getRandom(min, max) {
3
return Math.floor(Math.random() * (max - min) + min)
4
}
5
export { getRandom }
6
7
// cards.js
8
import { getRandom } from './random.js'
9
const getRandomCard = (cards) => {
10
const randomCardIndex = getRandom(0, array.length)
11
return cards[randomCardIndex]
12
}
13
export { getRandomCard }
14
15
// cards.test.js
16
import * as randomGenerator from './random.js'
17
import { getRandomCard } from './cards.js'
18
test('Returns 7♥', () => {
19
const originalImplementation = randomGenerator.getRandom
20
randomGenerator.getRandom = jest.fn(() => 2)
21
const result = getRandomCard(['2♣', 'K♦️', '7♥', '3♠'])
22
expect(result).toBe('7♥')
23
expect(randomGenerator.getRandom).toHaveBeenCalledTimes(1)
24
expect(randomGenerator.getRandom).toHaveBeenCalledWith(0, 4)
25
// we keep the test idempotent
26
randomGenerator.getRandom = originalImplementation
27
})
Copied!
为了实现上面的效果,我们可以这样模拟定义jest.fnexpect
1
// jest.fn
2
function fn(impl) {
3
const mockFn = (...args) => {
4
mockFn.mock.calls.push(args)
5
return impl(...args)
6
}
7
mockFn.mock = {calls: []}
8
return mockFn
9
}
10
11
// expect
12
import assert from 'assert'
13
function expect(current) {
14
return {
15
toHaveBeenCalledTimes(nrTimesExpected) {
16
if (current.mock.calls.length !== nrTimesExpected) {
17
throw new Error(`
18
Expected: ${expected}
19
Called: ${func.mock.calls.length}
20
`)
21
}
22
},
23
toHaveBeenCalledWith(...params) {
24
// this is a simplified version
25
if (!assert.deepStrictEqual(current.mock.calls[0], ...params)) {
26
throw new Error(`
27
Expected: ${expected}
28
Called: ${func.mock.calls.length}
29
`)
30
}
31
}
32
}
33
}
Copied!
最近更新 1yr ago
复制链接