Simulation des temporisateurs
Cette page a été traduite par PageTurner AI (bêta). Non approuvée officiellement par le projet. Vous avez trouvé une erreur ? Signaler un problème →
Les fonctions natives de temporisation (comme setTimeout(), setInterval(), clearTimeout(), clearInterval()) sont peu adaptées aux environnements de test car elles dépendent de l'écoulement du temps réel. Jest peut remplacer ces temporisateurs par des fonctions qui vous permettent de contrôler le passage du temps. Great Scott !
Voir aussi la documentation de l'API des faux temporisateurs.
Activer les faux temporisateurs
Dans l'exemple suivant, nous activons les faux temporisateurs en appelant jest.useFakeTimers(). Cela remplace l'implémentation originale de setTimeout() et autres fonctions de temporisation. Les temporisateurs peuvent être restaurés à leur comportement normal avec jest.useRealTimers().
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);
});
Exécuter tous les temporisateurs
Un autre test que nous pourrions écrire pour ce module vérifie que le callback est appelé après 1 seconde. Pour cela, nous utiliserons les API de contrôle des temporisateurs de Jest pour accélérer le temps au milieu du test :
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);
});
Exécuter les temporisateurs en attente
Il existe aussi des scénarios où vous pourriez avoir un temporisateur récursif - c'est-à-dire un temporisateur qui programme un nouveau temporisateur dans son propre callback. Dans ce cas, exécuter tous les temporisateurs créerait une boucle infinie, générant l'erreur : "Aborting after running 100000 timers, assuming an infinite loop !"
Si c'est votre cas, utiliser jest.runOnlyPendingTimers() résoudra le problème :
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);
});
});
Pour le débogage ou toute autre raison, vous pouvez modifier la limite de temporisateurs exécutés avant qu'une erreur ne soit lancée :
jest.useFakeTimers({timerLimit: 100});
Avancer les temporisateurs d'un temps donné
Une autre possibilité est d'utiliser jest.advanceTimersByTime(msToRun). Quand cette API est appelée, tous les temporisateurs sont avancés de msToRun millisecondes. Toutes les "macro-tâches" en attente mises en file via setTimeout() ou setInterval(), et qui devraient s'exécuter dans cet intervalle, seront exécutées. De plus, si ces macro-tâches programment de nouvelles macro-tâches devant s'exécuter dans le même intervalle, celles-ci seront aussi exécutées jusqu'à ce qu'il ne reste plus de macro-tâches dans la file devant être traitées dans les msToRun millisecondes.
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);
});
Enfin, il peut parfois être utile dans certains tests de pouvoir effacer tous les temporisateurs en attente. Pour cela, nous disposons de jest.clearAllTimers().
Avancer jusqu'à la prochaine frame d'animation
Dans les applications, on souhaite souvent planifier du travail dans une frame d'animation (avec requestAnimationFrame). Nous exposons une méthode pratique jest.advanceTimersToNextFrame() pour avancer suffisamment les temporisateurs afin d'exécuter toutes les frames d'animation planifiées.
Pour les simulations temporelles, les frames d'animation s'exécutent toutes les 16ms (correspondant à environ 60 images par seconde) après le démarrage de l'horloge. Quand vous planifiez un callback dans une frame d'animation (avec requestAnimationFrame(callback)), le callback sera appelé quand l'horloge aura avancé de 16ms. jest.advanceTimersToNextFrame() avancera l'horloge juste assez pour atteindre le prochain incrément de 16ms. Si l'horloge a déjà avancé de 6ms depuis la planification du callback d'une frame d'animation, alors l'horloge sera avancée de 10ms.
jest.useFakeTimers();
it('calls the animation frame callback after advanceTimersToNextFrame()', () => {
const callback = jest.fn();
requestAnimationFrame(callback);
// At this point in time, the callback should not have been called yet
expect(callback).not.toHaveBeenCalled();
jest.advanceTimersToNextFrame();
// Now our callback should have been called!
expect(callback).toHaveBeenCalled();
expect(callback).toHaveBeenCalledTimes(1);
});
Falsification sélective
Parfois votre code peut nécessiter d'éviter d'écraser l'implémentation originale d'une API particulière. Dans ce cas, vous pouvez utiliser l'option doNotFake. Par exemple, voici comment vous pourriez fournir une fonction mock personnalisée pour performance.mark() dans un environnement jsdom :
/**
* @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);
});