Skip to content

2026-10-02

/ 14 分鐘閱讀

/ Testing Fundamentals

Test Double 概觀:mock 與 spy 是什麼,以及 restore、reset、clear 的差異 ​

進入新的章節「Stubs, Spies, and Mocks」,這篇先從最上位的概念 Test Double 開始,說明為什麼測試常常需要用假的東西取代真實的外部依賴。

Test Double:mock、spy、stub 共用的上位概念 ​

Test Double 是一個籠統的統稱,涵蓋 mock、spy、stub,以及其他各種在測試裡拿來模擬、造假的東西。講師坦言,如果不是為了準備課程,他自己也常常搞不清楚這些詞彼此的差異。這些名詞本質上都是同一個概念的不同變化,差別主要在外觀跟方法名稱,術語本身不是重點,概念上抓到就好。

為什麼需要它:把程式碼獨立出來測試 ​

核心目的是把程式碼放在隔離的環境裡測試。實際的應用程式通常會呼叫 API、處理時間、用到 console.log 或 fetch,或是依賴某個第三方套件,但測試不需要連帶去驗證別人寫的程式碼對不對,真正該確認的是「我改了自己的程式碼之後,它還能不能正常運作」。

有些情況下沒辦法完全掌控外部世界,這時候想確認的往往不是回傳值,而是「這個東西有沒有被正確呼叫」,例如某個函式應該要呼叫 alert 或 fetch,測試想驗證的就是它有沒有真的被呼叫、呼叫時帶的參數對不對。理想上程式碼都應該是純函式(idempotent):東西進去、回傳值出來,沒有任何副作用。但操作 DOM 是副作用,寫 log 也是副作用,幾乎所有重要的事情都是副作用,這就是寫程式的現實,測試有時候免不了要處理外部世界的東西。

用力過猛的代價:這是一場取捨遊戲 ​

擁有這些能替換外部依賴的能力,不代表可以無限上綱地用。前面課程已經提醒過,如果每個東西都要模擬、控制,最後只會做出一堆沒有意義的測試;就像稍早提到隨機值的例子(擲骰子、ID 產生器),有時候只是想讓某些原本不可預期的東西暫時固定下來而已。

要不要用 test double,很大程度是一場取捨遊戲,答案常常是「看情況」:

  • 如果能快速準備一份乾淨的後端資料狀態給單一測試用,測完再重置回乾淨狀態,而且整個流程接進測試流程裡並不痛苦,那當然值得這麼做
  • 如果選擇真的去模擬網路請求,代價是這也意味著要自己掌控整台伺服器的行為。講師提過好幾次遇到的實際狀況:後端團隊在 staging 環境改壞了一支 API,結果自己的測試也跟著壞掉;模擬網路請求確實犧牲了一些真實性,但如果測試會因為別人改了一個跟自己程式碼無關的 CSS 而失敗,也不是什麼好事
  • 不用每次測試都真的啟動、關閉伺服器,也能少一個可能出錯的環節
  • 時間相關的東西同樣是老問題

這些考量沒有哪個是絕對正確的鐵律,更像是各自情境底下的利弊權衡。

Mock 跟 Spy 分別在做什麼 ​

  • Mock:偽造一個外部的東西,讓程式碼在測試裡呼叫到的不是真正的實作
  • Spy:讓你可以在旁邊觀察一個函式,記錄它有沒有被呼叫、呼叫時帶了什麼參數。特別適合用在函式本身沒有回傳值、但內部呼叫了某個內建或第三方函式庫的情況,這時候沒有回傳值可以直接斷言,只能靠 spy 確認它確實做了該做的事

如果聽到這裡會想到「這不是可以直接用依賴注入,把值傳進去就好了嗎」,這個想法完全正確:需要的時候再用 test double,但用之前要仔細想清楚是不是真的需要。

測試後怎麼把東西還原:clear、reset、restore 的差異 ​

替換掉一個外部依賴之後,測試結束後通常需要把它還原,這裡有幾種做法:

  • restore:把原本的實作放回去,例如原本 mock 掉的 fetch,呼叫這個之後就會變回真正的 fetch,之後有需要還是可以再重新 mock 一次
  • reset:保留現有的 mock 本身,但把它的實作清空、狀態重置,記錄下來的呼叫紀錄也一併清掉
  • clear:單純把記錄下來的呼叫紀錄清掉,mock 本身的行為維持不變

restore 涵蓋了 reset 跟 clear 做的事,再加上把原始實作復原這一步。如果不確定該用哪一個,先用 restore 就對了,等真的遇到需要細分的情境再回頭研究其他兩個的差異。實務上常見的做法是把這些還原邏輯放進 beforeEach,每個測試開始前先把上一個測試留下的東西清乾淨。

為什麼會有這些機制:從「共用同一個 process」到「測試隔離」 ​

在比較舊的測試環境裡,所有測試都在同一個 process 裡執行,一個測試如果 mock 掉了一堆東西又忘記清乾淨,後面的測試就會沿用這個被污染過的狀態繼續跑,出錯了也很難追。現在主流的測試執行環境(包括 Vitest 跟 Jest)都內建了測試隔離:每一個測試都在獨立的環境裡執行,互相污染的風險比以前低很多。不過如果專案裡還在用比較舊的工具鏈,這些還原機制的重要性就會相對高一些。

底層其實都是同一批老字號函式庫 ​

雖然 Test Double 這一整套概念聽起來很新,但背後用的東西其實一點都不新。Vitest 底層用的 mocking、stubbing 函式庫是 Sinon,一個超過十年歷史的老函式庫;前面一直在用的 expect 斷言語法,來自另一個同樣有十幾年歷史的斷言函式庫 Chai;Jest 則是包了一層在有十五年歷史的測試執行器 Jasmine 上面。也就是說,Vitest 跟 Jest 很大程度只是在這些老函式庫外面包了一層,底層的運作方式其實沒有真的變過。這也是為什麼講師會說術語上的差異不重要,因為不同工具鏈骨子裡處理這些事情的方式其實很相似。有時候在編輯器裡把游標移到這些方法上,型別提示(IntelliSense)也會露出底層真正呼叫到的函式庫。

複習 ​

測試裡用來統稱各種模擬、假造之物的上位詞彙是什麼? ​

Test Double

在測試情境裡,spy 是什麼? ​

一種可以觀察函式的方法,用來確認它被呼叫時帶了什麼參數,特別適合用在函式本身沒有回傳值、但內部呼叫了其他函式的情況

使用 test double 的主要目的是什麼? ​

讓程式碼可以在隔離的環境下測試,不需要依賴 API、跟時間有關的函式,或第三方函式庫這類外部依賴

為什麼現代的測試執行器比較不用擔心 mock 污染的問題? ​

Vitest、Jest 這類現代測試執行器大多內建測試隔離機制,每個測試都在獨立的環境裡執行,降低了前一個測試殘留的 mock 狀態影響到後面測試的風險

什麼是 test double,有哪些常見的例子? ​

Test double 是測試裡用來取代真實依賴的各種模擬、假造物件的統稱。常見的例子包括 mock(偽造一個外部的東西)、spy(觀察一個函式、確認它被呼叫時帶了什麼參數)跟 stub,這些都能讓程式碼在不依賴外部 API、第三方函式庫,或是像時間這種難以預測的因素之下進行測試

小測驗 ​

測試裡用來統稱各種模擬、假造之物的上位詞彙是什麼? Test Doubles
在測試術語裡,「spy」指的是什麼? 一個用來觀察、追蹤方法呼叫情形的函式
使用 test double 的主要目的是什麼? 讓程式碼在隔離的環境下測試,並控制外部依賴
為什麼 Vitest 這類現代測試執行器會提供測試隔離機制? 避免測試之間因為彼此的 mock 依賴互相干擾
在單元測試裡,使用 test double 的主要目的是什麼? 藉由模擬 API、時間、第三方函式庫這類外部依賴,讓程式碼能在隔離的環境下測試

此文章是 FrontendMasters 上的 Testing Fundamentals 課程筆記

最後更新時間:

Buy Me A Coffee

系列章節 第 19 篇 / 共 27 篇

0 %
MIT Licensed | Copyright © 2025-present Wen-Hsiu's Blog
Photo by Federica Galli on Unsplash