單元測試時,假設我們要測試 A 單元,但這個單元需要依賴另一個單元(B 單元),例如:發送一個 request 到 API 。一方面,因為單元測試要盡量隔離需要被測試的單元(A 單元),讓我們純粹測試單元自身內部的功能是否如預期;另一方面,如果每次測試都要發送 request,這樣可能會徒增執行時間跟不必要的開銷,反而讓測試成本變得很大。這時候,我們就需要 Mock 了,簡單來說就是爲依賴的單元(B 單元)做個「替身」,這個替身是可以被決定的,好讓我們可以預期每次的結果。
假設我們有個 thumbWar
module,接收 player1
跟 player2
兩個參數,回傳先贏兩回的 winner
。其中, thumbWar
依賴 utils.getWinner
決定誰是每回的贏家。
thumb-war.js
const utils = require('./utils')
function thumbWar(player1, player2) {
const numberToWin = 2
let player1Wins = 0
let player2Wins = 0
while (player1Wins < numberToWin && player2Wins < numberToWin) {
const winner = utils.getWinner(player1, player2)
if (winner === player1) {
player1Wins++
} else if (winner === player2) {
player2Wins++
}
}
return player1Wins > player2Wins ? player1 : player2
}
module.exports = thumbWar
我們來嘗試將 utils.getWinner
mock 起來,確保 thumbWar
的測試結果一致。
現在先建立一個 monkey-patching.js
檔案,呼叫 thumbWar
然後驗證 winner 是 小夫
。
monkey-patching.js
const assert = require('assert')
const thumbWar = require('../thumb-war')
const utils = require('../utils')
const winner = thumbWar('小夫', '胖虎')
assert.strictEqual(winner, '小夫')
但 thumbWar
裡面依賴 utils.getWinner
隨機產生的結果,這個結果不是現在要測試的重點,因此我們將 utils.getWinner
暫時更改為另一個 function,一樣接受兩個參數,但永遠都回傳同一個結果。
monkey-patching.js
utils.getWinner = (p1, p2) => p1
const winner = thumbWar('小夫', '胖虎')
assert.strictEqual(winner, '小夫')
最後,必須將 utils.getWinner
改回原本的 method,才不會影響到其他需要用到 utils.getWinner
的測試:
const originalGetWinner = utils.getWinner // 儲存原本的 method
utils.getWinner = (p1, p2) => p1
const winner = thumbWar('小夫', '胖虎')
assert.strictEqual(winner, '小夫')
utils.getWinner = originalGetWinner // 改回原本的 method
以上這種更改 object property 的方式,通常稱為 monkey patching。