メインコンテンツへスキップ
バージョン: 29.7

タイマーモック

非公式ベータ版翻訳

このページは PageTurner AI で翻訳されました(ベータ版)。プロジェクト公式の承認はありません。 エラーを見つけましたか? 問題を報告 →

ネイティブのタイマー関数(setTimeout()setInterval()clearTimeout()clearInterval()など)は、実際の時間経過に依存するためテスト環境には理想的ではありません。Jestはタイマーを置換する機能を提供しており、時間の経過を制御できるようになります。すごい!

情報

フェイクタイマーAPIのドキュメントも参照してください。

フェイクタイマーの有効化

次の例ではjest.useFakeTimers()を呼び出してフェイクタイマーを有効にします。これによりsetTimeout()や他のタイマー関数の本来の実装が置き換えられます。jest.useRealTimers()で通常の動作に戻せます。

timerGame.js
function timerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up -- stop!");
callback && callback();
}, 1000);
}

module.exports = timerGame;
__tests__/timerGame-test.js
jest.useFakeTimers();
jest.spyOn(global, 'setTimeout');

test('waits 1 second before ending the game', () => {
const timerGame = require('../timerGame');
timerGame();

expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
});

すべてのタイマーの実行

このモジュールで書くべき別のテストとして、1秒後にコールバックが呼ばれることを確認するテストがあります。これを行うには、テストの途中でJestのタイマー制御APIを使って時間を早送りします:

jest.useFakeTimers();
test('calls the callback after 1 second', () => {
const timerGame = require('../timerGame');
const callback = jest.fn();

timerGame(callback);

// At this point in time, the callback should not have been called yet
expect(callback).not.toHaveBeenCalled();

// Fast-forward until all timers have been executed
jest.runAllTimers();

// Now our callback should have been called!
expect(callback).toHaveBeenCalled();
expect(callback).toHaveBeenCalledTimes(1);
});

保留中のタイマーの実行

再帰タイマー(コールバック内で新しいタイマーを設定するタイマー)が存在するシナリオもあります。この場合、すべてのタイマーを実行すると無限ループになり、次のエラーが発生します:「100000個のタイマー実行後に中止しました。無限ループと判断しました!」

この場合、jest.runOnlyPendingTimers()を使えば問題を解決できます:

infiniteTimerGame.js
function infiniteTimerGame(callback) {
console.log('Ready....go!');

setTimeout(() => {
console.log("Time's up! 10 seconds before the next game starts...");
callback && callback();

// Schedule the next game in 10 seconds
setTimeout(() => {
infiniteTimerGame(callback);
}, 10000);
}, 1000);
}

module.exports = infiniteTimerGame;
__tests__/infiniteTimerGame-test.js
jest.useFakeTimers();
jest.spyOn(global, 'setTimeout');

describe('infiniteTimerGame', () => {
test('schedules a 10-second timer after 1 second', () => {
const infiniteTimerGame = require('../infiniteTimerGame');
const callback = jest.fn();

infiniteTimerGame(callback);

// At this point in time, there should have been a single call to
// setTimeout to schedule the end of the game in 1 second.
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);

// Fast forward and exhaust only currently pending timers
// (but not any new timers that get created during that process)
jest.runOnlyPendingTimers();

// At this point, our 1-second timer should have fired its callback
expect(callback).toHaveBeenCalled();

// And it should have created a new timer to start the game over in
// 10 seconds
expect(setTimeout).toHaveBeenCalledTimes(2);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 10000);
});
});
メモ

デバッグなどでエラー発生前のタイマー実行制限を変更できます:

jest.useFakeTimers({timerLimit: 100});

時間を指定してタイマーを進める

別の方法としてjest.advanceTimersByTime(msToRun)があります。このAPIを呼び出すと、すべてのタイマーがmsToRunミリ秒進みます。setTimeout()やsetInterval()でキューイングされた保留中の「マクロタスク」で、この時間枠内に実行されるものはすべて実行されます。さらに、これらのマクロタスクが同じ時間枠内で実行される新しいマクロタスクをスケジュールした場合、msToRunミリ秒以内に実行すべきキュー内のマクロタスクがなくなるまで実行が続きます。

timerGame.js
function timerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up -- stop!");
callback && callback();
}, 1000);
}

module.exports = timerGame;
__tests__/timerGame-test.js
jest.useFakeTimers();
it('calls the callback after 1 second via advanceTimersByTime', () => {
const timerGame = require('../timerGame');
const callback = jest.fn();

timerGame(callback);

// At this point in time, the callback should not have been called yet
expect(callback).not.toHaveBeenCalled();

// Fast-forward until all timers have been executed
jest.advanceTimersByTime(1000);

// Now our callback should have been called!
expect(callback).toHaveBeenCalled();
expect(callback).toHaveBeenCalledTimes(1);
});

最後に、保留中のタイマーをすべてクリアできると便利な場合があります。その場合はjest.clearAllTimers()を使用します。

選択的なフェイク処理

特定のAPIの本来の実装を上書きしたくない場合があります。その場合はdoNotFakeオプションを使用します。例えばjsdom環境でperformance.mark()のカスタムモック関数を提供する方法は次の通りです:

/**
* @jest-environment jsdom
*/

const mockPerformanceMark = jest.fn();
window.performance.mark = mockPerformanceMark;

test('allows mocking `performance.mark()`', () => {
jest.useFakeTimers({doNotFake: ['performance']});

expect(window.performance.mark).toBe(mockPerformanceMark);
});