通常,在測試 JavaScript 跟 Mock 依賴的時候,我們需要確認 Functions 是不是有被正確地呼叫,例如:被呼叫了幾次?參數是否有正常傳遞進去?
前面我們在 monkey-patching.js
這個檔案裡面只是單純將 utils.getWinner
改為另一個 function,但如果我們要做到能測試 function 是否有被正確呼叫的話,這樣是有限的。
Jest 提供了一個 build-in 方法 jest.fn ,你可以傳入一個 function 給 jest.fn
,回傳的就叫做 mock function,Jest 爲 mock function 提供一些 API,方便我們測試追蹤 function 是否有被正確呼叫:
utils.getWinner = jest.fn((p1, p2) => p1)
假設我們現在用 Jest 來寫測試,有一個資料夾 __test__
裡面有一個測試檔案 mock-fn.js
, 試著來寫一個測試,除了驗證 thumbWar
結果之外,也要驗證 utils.getWinner
是否有被正確呼叫:
test/mock-fn.js
const thumbWar = require('../thumb-war')
const utils = require('../utils')
test('returns winner', () => {
const originalGetWinner = utils.getWinner
utils.getWinner = jest.fn((p1, p2) => p1) // mock function
const winner = thumbWar('小夫', '胖虎')
expect(winner).toBe('小夫')
expect(utils.getWinner).toHaveBeenCalledTimes(2) // utils.getWinner 被呼叫兩次
expect(utils.getWinner).toHaveBeenCalledWith('小夫', '胖虎') // utils.getWinner 是否傳入正確的參數
expect(utils.getWinner).toHaveBeenNthCalledWith(
1,
'小夫',
'胖虎'
) // utils.getWinner 第一次呼叫傳入的參數
expect(utils.getWinner).toHaveBeenNthCalledWith(
2,
'小夫',
'胖虎'
) // utils.getWinner 第二次呼叫傳入的參數
utils.getWinner = originalGetWinner
})
關於 Jest 所提供的方法,詳細可參考 官方文件
剛剛提到 Jest 有提供 mock function 一些實用的 API,例如: mockFn.mock.calls ,它提供一個 array,裡面包含了 mock function 每次被呼叫的參數,現在我們用這個 API 讓測試程式碼更簡潔:
test/mock-fn.js
test('returns winner', () => {
const originalGetWinner = utils.getWinner
utils.getWinner = jest.fn((p1, p2) => p1) // mock function
const winner = thumbWar('小夫', '胖虎')
expect(winner).toBe('小夫')
expect(utils.getWinner.mock.calls).toEqual([
[ '小夫', '胖虎' ],
[ '小夫', '胖虎' ]
]) // 現在只需要這樣
utils.getWinner = originalGetWinner
})
為了更理解工具的原理,現在我們來試試不用 Jest,自己做一個跟 jest.fn
差不多的功能。
在 no-framework
資料夾中,創建一個 mock-fn.js
檔案,宣告一個叫做 fn
的 function,這個 function 跟 jest.fn
一樣可以 return 一個 mock function:
no-framework/mock-fn.js
function fn(impl) {
const mockFn = (...args) => {
return impl(...args)
}
return mockFn
}
接下來,我們要像 Jest 的 mock function API 一樣,提供一個屬性 mock.calls
,透過這個屬性取得每次呼叫時傳入的參數:
no-framework/mock-fn.js
function fn(impl) {
const mockFn = (...args) => {
mockFn.mock.calls.push(args)
return impl(...args)
}
mockFn.mock = {calls: []}
return mockFn
}
最後,我們不用 Jest 所寫的測試,如下:
no-framework/mock-fn.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: []}
return mockFn
}
const originalGetWinner = utils.getWinner
utils.getWinner = fn((p1, p2) => p1)
const winner = thumbWar('小夫', '胖虎')
assert.strictEqual(winner, '小夫')
assert.deepStrictEqual(utils.getWinner.mock.calls, [
['小夫', '胖虎'],
['小夫', '胖虎']
])
utils.getWinner = originalGetWinner
今天我們學習如何測試 function 是否被正確地呼叫。透過 Jest 的 jest.fn
回傳 mock function,以及 mock function 裡的 mock.calls
屬性,驗證呼叫次數、每次呼叫所傳入的參數。最後,動手實作一個簡易的 function fn
,模仿 jest.fn
的功能,理解背後的原理。