到目前為止,我們還是「手動」地記住及復原需要被 mock 的 function,例如:
test('returns winner', () => {
const originalGetWinner = utils.getWinner
utils.getWinner = jest.fn((p1, p2) => p1)
...
utils.getWinner = originalGetWinner
})
但這樣其實滿不方便的,而 Jest 提供的 jest.spyOn 可以幫我們避免必須「手動」復原的情況。jest.spyOn
接收兩個參數,第一個是模組,第二個是 method 的名稱。
現在在 __test__
資料夾新建一個檔案叫做 spy.js
,將 昨日文章 所寫的測試 __test__/mock-fn.js
內容複製到新檔案內,使用 jest.spyOn
來改寫。
jest.spyOn
會將 utils
module 裡的 getWinner
method 置換成一個「空的」mock function。我們不再需要 originalGetWinner
來記住原本的 function,而且搭配 jest.spyOn
來創建 mock function,可以使用 mockFn.mockRestore 這個 method 來復原回原本的 function:
test/spy.js
test('returns winner', () => {
jest.spyOn(utils, 'getWinner') // utils.getWinner 被取代成一個空的 mock function
utils.getWinner = jest.fn((p1, p2) => p2)
...
utils.getWinner.mockRestore() // 復原回原本的 function
})
Jest 的 Mock function 有一個 method 叫做 mockFn.mockImplementation,前面所使用過的 jest.fn(fn)
就是 jest.fn().mockImplementation(fn)
的簡單表示,現在我們使用 mockImplementation
來取代 jest.fn(fn)
:
test/spy.js
test('returns winner', () => {
jest.spyOn(utils, 'getWinner')
utils.getWinner.mockImplementation((p1, p2) => p2) // 改為使用 mockImplementation
...
utils.getWinner.mockRestore()
})
根據文件:
mockFn.mockRestore
只有在 mock 是由jest.spyOn
創建才有作用。另外,如果只有單純使用jest.fn()
來創建 mock function 的話,只能「手動」復原。
為了更理解工具的原理,現在我們來試試不用 Jest,自己做一個跟 **jest.spyOn
** 差不多的功能。
現在在 no-framework
資料夾新建一個檔案叫做 spy.js
,將 昨日文章 所寫的測試 no-framework/mock-fn.js
內容複製到新檔案內來改寫。
現在新增一個 function 叫 spyOn
,接收 object
跟 prop
兩個參數,加入 const originalValue = obj[prop]
記住原始的 function,然後將 obj[prop]
設為呼叫 fn()
所回傳的「空的」 mock function。
no-framework/spy.js
function spyOn(obj, prop) {
const originalValue = obj[prop] // 記住原始的 function
obj[prop] = fn() // mock function
}
還記得昨天寫的 fn
function 嗎?我們需要給它一個預設的參數值,一個空的 arrow function () => {}
:
no-framework/spy.js
function fn(impl = () => {}) { // 設置預設的參數值為空的 arrow function
const mockFn = (...args) => {
mockFn.mock.calls.push(args)
return impl(...args)
}
mockFn.mock = {calls: []}
return mockFn
}
現在我們來實作用 mockRestore
復原 mock function 的部分。為 obj[prop]
新增一個 method 叫 mockRestore
,賦予 () => (obj[prop] = originalValue)
:
no-framework/spy.js
function spyOn(obj, prop) {
const originalValue = obj[prop]
obj[prop] = fn()
obj[prop].mockRestore = () => (obj[prop] = originalValue) // 復原 mock function
}
目前我們透過 spyOn
裡面呼叫 fn()
所回傳的 mock function 是「空的」arrow function,接下來要為 fn
function 加入一個 method mockImplementation
,讓我們可以透過它傳入 mock function:
no-framework/spy.js
function fn(impl = () => {}) {
const mockFn = (...args) => {
mockFn.mock.calls.push(args)
return impl(...args)
}
mockFn.mock = {calls: []}
mockFn.mockImplementation = newImpl => (impl = newImpl) // mock function
return mockFn
}
最後,我們不用 Jest 所寫的測試,如下:
no-framework/spy.js
const assert = require('assert')
const thumbWar = require('../thumb-war')
const utils = require('../utils')
function fn(impl = () => {}) {
const mockFn = (...args) => {
mockFn.mock.calls.push(args)
return impl(...args)
}
mockFn.mock = {calls: []}
mockFn.mockImplementation = newImpl => (impl = newImpl)
return mockFn
}
function spyOn(obj, prop) {
const originalValue = obj[prop]
obj[prop] = fn()
obj[prop].mockRestore = () => (obj[prop] = originalValue)
}
spyOn(utils, 'getWinner')
utils.getWinner.mockImplementation((p1, p2) => p1)
const winner = thumbWar('小夫', '胖虎')
assert.strictEqual(winner, '小夫')
assert.deepStrictEqual(utils.getWinner.mock.calls, [
['小夫', '胖虎'],
['小夫', '胖虎']
])
// cleanup
utils.getWinner.mockRestore()
Jest 提供的 jest.spyOn
可以幫我們 mock function 時,更方便地處理記住原始 function,並且用 mockRestore
來復原原始 function。另外,搭配 mockFn.mockImplementation
method 來 mock function。最後,動手實作一個簡易的 function spyOn
,模仿 jest.spyOn
的功能,理解背後的原理。