Timer-Mocks
Diese Seite wurde von PageTurner AI übersetzt (Beta). Nicht offiziell vom Projekt unterstützt. Fehler gefunden? Problem melden →
Die nativen Timer-Funktionen (z.B. setTimeout(), setInterval(), clearTimeout(), clearInterval()) eignen sich weniger für Testumgebungen, da sie auf das Verstreichen echter Zeit angewiesen sind. Jest kann Timer durch Funktionen ersetzen, die dir erlauben, den Zeitverlauf zu kontrollieren. Großartig!
Siehe auch die Dokumentation zur Fake Timers API.
Fake Timer aktivieren
Im folgenden Beispiel aktivieren wir Fake Timer durch Aufruf von jest.useFakeTimers(). Dadurch wird die ursprüngliche Implementierung von setTimeout() und anderen Timer-Funktionen ersetzt. Timer können mit jest.useRealTimers() wieder auf ihr normales Verhalten zurückgesetzt werden.
function timerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up -- stop!");
callback && callback();
}, 1000);
}
module.exports = timerGame;
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);
});
Alle Timer ausführen
Ein weiterer Test für dieses Modul könnte prüfen, ob der Callback nach 1 Sekunde aufgerufen wird. Dazu verwenden wir Jest's Timer-Control-APIs, um die Zeit während des Tests vorzuspulen:
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);
});
Ausstehende Timer ausführen
Es gibt auch Szenarien mit rekursiven Timern – ein Timer, der in seinem eigenen Callback einen neuen Timer setzt. Hier würde das Ausführen aller Timer zu einer Endlosschleife führen, die diesen Fehler verursacht: "Aborting after running 100000 timers, assuming an infinite loop!"
In diesem Fall löst jest.runOnlyPendingTimers() das Problem:
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;
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);
});
});
Zu Debugging-Zwecken oder aus anderen Gründen kannst du das Limit der Timer ändern, die vor einem Fehler ausgeführt werden:
jest.useFakeTimers({timerLimit: 100});
Timer um Zeit vorrücken
Alternativ kannst du jest.advanceTimersByTime(msToRun) verwenden. Bei Aufruf dieser API werden alle Timer um msToRun Millisekunden vorgerückt. Alle ausstehenden "Makro-Tasks", die via setTimeout() oder setInterval() in der Warteschlange stehen und in diesem Zeitraum ausgeführt würden, werden ausgeführt. Falls diese Makro-Tasks neue Makro-Tasks planen, die im selben Zeitfenster ausgeführt werden sollen, werden auch diese ausgeführt, bis keine Tasks mehr in der Warteschlange sind.
function timerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up -- stop!");
callback && callback();
}, 1000);
}
module.exports = timerGame;
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);
});
Gelegentlich kann es nützlich sein, alle ausstehenden Timer zu löschen. Dafür steht jest.clearAllTimers() zur Verfügung.
Selektives Faking
Manchmal muss dein Code verhindern, dass die Originalimplementierung bestimmter APIs überschrieben wird. In diesem Fall kannst du die Option doNotFake verwenden. Beispielsweise kannst du so in einer jsdom-Umgebung eine benutzerdefinierte Mock-Funktion für performance.mark() bereitstellen:
/**
* @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);
});