7-3 Timer怎麼測?


Posted by RitaYang0811 on 2025-07-30

問題:

我們平常在寫前端時,常常會用到 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 狀態變化、確保計時器有被清掉,是前端測試中計時功能的三大核心。


#QA #Unit Test







Related Posts

[15] 值 - NaN、Infinity

[15] 值 - NaN、Infinity

DAY11:Disemvowel Trolls

DAY11:Disemvowel Trolls

Day02:從判斷式看 bytecode

Day02:從判斷式看 bytecode


Comments