見出し画像

【Jest_47】 setTimeoutやsetIntervalをテストするには?

JestでsetTimeout/setIntervalをテストする


1. はじめに

setTimeoutやsetIntervalはJavaScriptの基本的な非同期タイマー関数ですが、Jestでテストするときは、

  • 実際に待機時間を使うとテストが遅くなる

  • タイマーの挙動を正確に検証したい

などの理由からタイマーをモック(偽装)して制御するのがベストプラクティスです。


2. Jestのタイマーモックの基本

Jestはタイマー関数をモック化でき、擬似的に時間を進められます。

jest.useFakeTimers();
  • これでsetTimeoutやsetIntervalがモック化される

  • タイマーのコールバックは手動で時間を進めない限り実行されない


3. 基本的なセットアップ

beforeEach(() => {
  jest.useFakeTimers();
});

afterEach(() => {
  jest.useRealTimers();
});
  • beforeEachでタイマーをモック化

  • afterEachで元に戻すのが推奨


4. setTimeoutのテスト例

4-1. テスト対象関数

function delayedCallback(callback) {
  setTimeout(() => {
    callback('done');
  }, 1000);
}

4-2. テストコード

test('delayedCallback calls callback after 1 second', () => {
  const mockCallback = jest.fn();

  delayedCallback(mockCallback);

  // タイマーはまだ実行されていない
  expect(mockCallback).not.toBeCalled();

  // タイマーを進める
  jest.advanceTimersByTime(1000);

  // コールバックが呼ばれたか検証
  expect(mockCallback).toBeCalledWith('done');
});
  • jest.advanceTimersByTime(ms) で指定ミリ秒分だけタイマーを進める

  • コールバックが呼ばれたかを検証


5. setIntervalのテスト例

5-1. テスト対象関数

function repeatCallback(callback) {
  let count = 0;
  const intervalId = setInterval(() => {
    count++;
    callback(count);
    if (count >= 3) clearInterval(intervalId);
  }, 500);
}

5-2. テストコード

test('repeatCallback calls callback 3 times', () => {
  const mockCallback = jest.fn();

  repeatCallback(mockCallback);

  expect(mockCallback).not.toBeCalled();

  // 500msごとにタイマーを進める
  jest.advanceTimersByTime(500);
  expect(mockCallback).toHaveBeenLastCalledWith(1);

  jest.advanceTimersByTime(500);
  expect(mockCallback).toHaveBeenLastCalledWith(2);

  jest.advanceTimersByTime(500);
  expect(mockCallback).toHaveBeenLastCalledWith(3);

  // もう呼ばれないことも検証できる
  jest.advanceTimersByTime(500);
  expect(mockCallback).toHaveBeenCalledTimes(3);
});

6. jest.runAllTimers()やjest.runOnlyPendingTimers()の活用

  • jest.runAllTimers(): 保留中のすべてのタイマーを即時実行

  • jest.runOnlyPendingTimers(): 現在保留中のタイマーのみ実行

test('runAllTimers example', () => {
  const mockCallback = jest.fn();
  delayedCallback(mockCallback);

  jest.runAllTimers();

  expect(mockCallback).toBeCalled();
});

7. async/awaitと組み合わせる場合のポイント

7-1. タイマーのモック中にPromiseを待つ例

test('async with timers', async () => {
  const mockCallback = jest.fn();

  delayedCallback(mockCallback);

  // タイマーを進めてコールバックを呼ぶ
  jest.advanceTimersByTime(1000);

  // Promiseの処理がある場合は待機
  await Promise.resolve();

  expect(mockCallback).toBeCalled();
});

8. 実環境のタイマーを使うテストに戻す

jest.useRealTimers();
  • テストによっては実タイマーを使いたい場合もある

  • useRealTimers() で戻せる


9. 注意点・よくある間違い

モックタイマーを使わずに待機する
 テストが遅くなる、動作が不安定になる
タイマーを進めずにアサーションする
 コールバックが呼ばれていないのでテストが失敗する
async関数との組み合わせでresolve待ちを忘れる
 非同期処理の完了がテストに反映されず誤検知の原因になる


10. まとめ

jest.useFakeTimers()
 タイマー関数をモック化し時間を制御できるjest.advanceTimersByTime(ms)
 指定時間だけタイマーを進める
jest.runAllTimers()
 保留中の全タイマーを即時実行
jest.useRealTimers()
 実際のタイマーに戻す
async/awaitとの併用
 await Promise.resolve()でイベントループ待ち合わせ



いいなと思ったら応援しよう!