Aller au contenu principal
Version: Suivant

Simulateurs de temporisation

Les fonctions de temporisation natives (c'est-à-dire setTimeout, setInterval, clearTimeout, clearInterval) sont moins adaptées à un environnement de test car elles dépendent du temps réel pour s'écouler. Jest peut remplacer les temporisateurs par des fonctions qui vous permettent de contrôler le temps qui passe. Great Scott !

timerGame.js
'use strict';
function timerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up -- stop!");
callback && callback();
}, 1000);
}
module.exports = timerGame;
__tests__/timerGame-test.js
'use strict';
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);
});

Ici, nous activons les faux temporisateurs en appelant jest.useFakeTimers(). Cela permet de remplacer setTimeout et d'autres fonctions de temporisation par des fonctions simulées. Les temporisateurs peuvent être restaurés à leur comportement normal avec jest.useRealTimers().

Bien que vous puissiez appeler jest.useFakeTimers() ou jest.useRealTimers() de n'importe où (niveau supérieur, à l'intérieur d'un bloc it, etc.), il s'agit d'une opération globale et elle affectera les autres tests du même fichier. De plus, vous devez appeler jest.useFakeTimers() pour réinitialiser les compteurs internes avant chaque test. Si vous prévoyez de ne pas utiliser de faux temporisateurs dans tous vos tests, vous devrez faire le nettoyage manuellement, car sinon les faux temporisateurs seront perdus dans les tests :

afterEach(() => {
jest.useRealTimers();
});
test('faire quelque chose avec de faux temporisateurs', () => {
jest.useFakeTimers();
// ...
});
test('faire quelque chose avec des temporisateurs réels', () => {
// ...
});

Exécuter tous les temporisateurs#

Un autre test que nous pourrions vouloir écrire pour ce module est celui qui affirme que le callback est appelé après 1 seconde. Pour ce faire, nous allons utiliser les API de contrôle des temporisateurs de Jest pour avancer rapidement dans le temps au beau milieu du test :

test('appelle le callback après 1 seconde', () => {
const timerGame = require('../timerGame');
const callback = jest.fn();
timerGame(callback);
// À ce point dans le temps, le callback ne devrait pas encore avoir été appelé.
expect(callback).not.toBeCalled();
// Avance rapide jusqu'à ce que toutes les temporisateurs aient été exécutés.
jest.runAllTimers();
// Maintenant notre callback aurait dû être appelé !
expect(callback).toBeCalled();
expect(callback).toHaveBeenCalledTimes(1);
});

Exécuter les temporisateurs en attente#

Il existe également des scénarios dans lesquels vous pouvez avoir un temporisateur récursif, c'est-à-dire un temporisateur qui définit un nouveau temporisateur dans son propre callback. Pour ces derniers, exécuter tous les temporisateurs serait une boucle sans fin... donc quelque chose comme jest.runAllTimers() n'est pas souhaitable. Pour ces cas, vous pouvez utiliser jest.runOnlyPendingTimers() :

infiniteTimerGame.js
'use strict';
function infiniteTimerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up! 10 secondes avant le début de la prochaine partie..." );
callback && callback();
// Planifie la prochaine partie dans 10 secondes
setTimeout(() => {
infiniteTimerGame(callback);
}, 10000);
}, 1000);
}
module.exports = infiniteTimerGame;
__tests__/infiniteTimerGame-test.js
'use strict';
jest.useFakeTimers();
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).toBeCalled();
// 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);
});
});

Avancer les temporisateurs par une durée#

Une autre possibilité est d'utiliser jest.advanceTimersByTime(msToRun). Lorsque cette API est appelée, tous les temporisateurs sont avancés de msToRun millisecondes. Toutes les « macro-tâches » en attente qui ont été mises en file d'attente via setTimeout() ou setInterval(), et qui devraient être exécutées pendant ce laps de temps, seront exécutées. En plus, si ces macro-tâches planifient de nouvelles macro-tâches qui devraient être exécutées dans le même laps de temps, celles-ci seront exécutées jusqu'à ce qu'il ne reste plus de macro-tâches dans la file d'attente qui doivent être exécutées dans les msToRun millisecondes.

timerGame.js
'use strict';
function timerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up -- stop!");
callback && callback();
}, 1000);
}
module.exports = timerGame;
it('appelle le callback après 1 seconde via advanceTimersByTime', () => {
const timerGame = require('../timerGame');
const callback = jest.fn();
timerGame(callback);
// À ce stade, le callback ne doit pas encore être appelé
expect(callback).not.toBeCalled();
// Avance rapide jusqu'à ce que toutes les temporisateurs aient été exécutés.
jest.advanceTimersByTime(1000);
// Maintenant notre callback devrait avoir été appelé !
expect(callback).toBeCalled();
expect(callback).toHaveBeenCalledTimes(1);
});

Enfin, il peut parfois être utile dans certains tests de pouvoir effacer tous les temporisateurs en attente. Pour cela, nous avons jest.clearAllTimers().

The code for this example is available at examples/timer.