問題:
我們平常在寫前端時,常常會用到 setTimeout() 或 setInterval(),比如按鈕點了後過幾秒執行某個動作,但如果在測試裡真的等幾秒,是非常沒有效率的,所以我們要使用假的timer進行測試。
解法:
我們可以用 jest.useFakeTimers()
來模擬時間,再用 jest.advanceTimersByTime(ms)
快轉時間,這樣可以瞬間跳過 setTimeout 的等待。
程式碼:
這段模擬一個「N 秒後要做事」的邏輯
我們要測試「它有沒有真的在預期時間後執行」
測試「它有沒有真的在預期時間後清除計時器」
//countdown.js
function main() {
const countEl = document.querySelector("#counter");
if (!countEl) return;
createCounter(countEl); //設置計時器
}
function createCounter(element, options = {}) {
let counter = 0;
let timer;
const { initialCount = 3, interval = 1000 } = options; // 定義初始計時器數值和間隔時間
setCounter(initialCount);
// 設置計時器數值
function setCounter(count) {
counter = count;
render();
}
// 渲染計時器到頁面
function render() {
element.innerHTML = `<button>${counter}</button>`;
}
// 渲染計時器結束提示
function renderStopAlert() {
element.innerHTML = `<h3 style="color:red">Time's Up!</h3>`;
}
// 設置倒數計時器
timer = setInterval(countdownCallback, interval);
function countdownCallback() {
if (counter <= 0) {
renderStopAlert();
return clearInterval(timer);
}
counter--;
render();
}
}
module.exports = { main, createCounter };
//countdown.test.js
const { createCounter } = require("./countdown");
jest.useFakeTimers();
let countEl;
const initialCount = 20;
const interval = 1000;
beforeEach(() => {
//建立假的DOM元素並取得該元素
document.body.innerHTML = `<div id="counter"></div>`;
countEl = document.querySelector("#counter");
createCounter(countEl, { initialCount, interval });
});
describe("倒數計時器功能測試", () => {
it("經過2秒後還有18秒", () => {
expect(countEl.innerHTML).toBe("<button>20</button>");
jest.advanceTimersByTime(2000); // 模擬經過2秒
expect(countEl.innerHTML).toBe("<button>18</button>"); // 期待計時器顯示18秒
});
it("倒數計時完畢後顯示結束提示", () => {
jest.advanceTimersByTime(21000); // 模擬經過21秒
expect(countEl.innerHTML).toBe(`<h3 style="color:red">Time's Up!</h3>`); // 期待計時器顯示結束提示
});
it("倒數計時完畢有啟動清除timer", () => {
const spyOnClearInterval = jest.spyOn(global, "clearInterval");
jest.advanceTimersByTime(21000); // 模擬經過21秒
expect(spyOnClearInterval).toHaveBeenCalled(); // 確認清除計時器被呼叫
expect(spyOnClearInterval).toHaveBeenCalledTimes(1); // 確認清除計時器被呼叫一次
spyOnClearInterval.mockRestore(); // 恢復原本的clearInterval方法
});
});
學習重點:
jest.useFakeTimers()
啟用模擬計時器(取代 setTimeout、setInterval 等)
jest.advanceTimersByTime(ms)
快轉假時間(觸發對應的 setTimeout / setInterval 回呼)
jest.spyOn(obj, methodName)
監聽物件的方法呼叫(這裡是 global.clearInterval)
可用 .toHaveBeenCalled() / .toHaveBeenCalledTimes(n) 驗證呼叫次數
mockRestore()
還原被 spy 改過的方法,避免影響後續測試
總結
用假時間(jest.useFakeTimers)快轉時鐘、驗證 DOM 狀態變化、確保計時器有被清掉,是前端測試中計時功能的三大核心。