Jest 是 Facebook 开源的一个 JavaScript 测试框架,已经应用在 Babel / TypeScript / Node / React / Angular / Vue 等项目中。
在愉悦地编写 Jest 测试代码后,你是否会好奇 test
/ expect
/ mock
/ spyOn
这些内置方法是如何实现的呢?下面我们一起来看一下。
Test
Jest 中的 test
函数标准实例是:
复制 test ( 'Passing test' , () => {})
// ✓ Passing test
复制 test ( 'Failing test' , () => new Error ( 'Error message' ))
// ✕ Failing test
// Error message
这里可以注意到,如果第二个参数的函数执行没有抛出异常的话,会打印第一个参数,因此我们的模拟实现可以是:
复制 function test (title , callback) {
try {
callback ()
console .log ( `✓ ${ title } ` )
} catch (error) {
console .error ( `✕ ${ title } ` )
console .error (error)
}
}
Expect
expect
函数的典型使用实例是:
复制 // 需要测试的函数
function multiply (a , b) {
return a * b
}
// 测试用例
test ( 'Multipling 3 by 4 is 12' , () => {
expect ( multiply ( 3 , 4 )) .toBe ( 12 )
} // ✓ Multipling 3 by 4 is 12
test ( 'Multipling 3 by 4 is 12' , () => {
expect ( multiply ( 3 , 4 )) .toBe ( 13 )
}) // ✕ Multipling 3 by 4 is 12
// Expected: 13
// Received: 12
注意到expect
的返回值具备一个可以调用的方法toBe
,那么我们可以这样模拟expect
复制 function expect (current) {
return {
toBe (expected) {
if (current !== expected) {
throw new Error ( `
Expected: ${ expected }
Received: ${ current }
` )
}
}
}
}
Mock
当我们要避免测试一个功能时又依赖于不稳定的另外一个功能,我们创建一个 Mock 来更好的管理。Jest 使用 jest.fn
是实现 Mock
复制 // random.js
function getRandom (min , max) {
return Math .floor ( Math .random () * (max - min) + min)
}
export { getRandom }
// cards.js
import { getRandom } from './random.js'
const getRandomCard = (cards) => {
const randomCardIndex = getRandom ( 0 , array . length )
return cards[randomCardIndex]
}
export { getRandomCard }
// cards.test.js
import * as randomGenerator from './random.js'
import { getRandomCard } from './cards.js'
test ( 'Returns 7♥' , () => {
const originalImplementation = randomGenerator .getRandom
randomGenerator .getRandom = jest .fn (() => 2 )
const result = getRandomCard ([ '2♣' , 'K♦️' , '7♥' , '3♠' ])
expect (result) .toBe ( '7♥' )
expect ( randomGenerator .getRandom) .toHaveBeenCalledTimes ( 1 )
expect ( randomGenerator .getRandom) .toHaveBeenCalledWith ( 0 , 4 )
// we keep the test idempotent
randomGenerator .getRandom = originalImplementation
})
为了实现上面的效果,我们可以这样模拟定义jest.fn
和 expect
复制 // jest.fn
function fn (impl) {
const mockFn = ( ... args) => {
mockFn . mock . calls .push (args)
return impl ( ... args)
}
mockFn .mock = {calls : []}
return mockFn
}
// expect
import assert from 'assert'
function expect (current) {
return {
toHaveBeenCalledTimes (nrTimesExpected) {
if ( current . mock . calls . length !== nrTimesExpected) {
throw new Error ( `
Expected: ${ expected }
Called: ${ func . mock . calls . length }
` )
}
} ,
toHaveBeenCalledWith ( ... params) {
// this is a simplified version
if ( ! assert .deepStrictEqual ( current . mock .calls[ 0 ] , ... params)) {
throw new Error ( `
Expected: ${ expected }
Called: ${ func . mock . calls . length }
` )
}
}
}
}