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 !
Consultez 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()
. Ceci remplace l'implémentation originale setTimeout()
et d'autres fonctions de temporisation. Les temporisateurs peuvent être restaurés à leur comportement normal avec jest.useRealTimers()
.
function timerGame(callback) {
console.log('Prêt .... partez !');
setTimeout(() => {
console.log("Le temps est écoulé -- arrêtez !");
callback && callback();
}, 1000);
}
module.exports = timerGame;
jest.useFakeTimers();
jest.spyOn(global, 'setTimeout');
test(''attend 1 seconde avant de mettre fin au jeu', () => {
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 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 :
jest.useFakeTimers();
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, l'exécution de tous les temporisateurs serait une boucle sans fin, ce qui entraînerait l'erreur suivante : « Aborting after running 100000 timers, assuming an infinite loop! »
Si c'est votre cas, l'utilisation de jest.runOnlyPendingTimers()
résoudra le problème :
function infiniteTimerGame(callback) {
console.log('Prêt .... partez !');
setTimeout(() => {
console.log("Le temps est écoulé ! 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;
jest.useFakeTimers();
jest.spyOn(global, 'setTimeout');
describe('infiniteTimerGame', () => {
test('planifie un minuteur de 10 secondes après 1 seconde', () => {
const infiniteTimerGame = require('../infiniteTimerGame');
const callback = jest.fn();
infiniteTimerGame(callback);
// A ce stade, il devrait y avoir un seul appel à
// setTimeout pour programmer la fin du jeu dans 1 seconde.
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
// Avance rapide et achèvement des temporisateurs en cours
// (mais pas les nouveaux temporisateurs créés au cours de ce processus).
jest.runOnlyPendingTimers();
// À ce stade, notre minuteur d'une seconde doit avoir déclenché son callback
expect(callback).toBeCalled();
// Et il devrait avoir créé un nouveau temporisateur pour commencer le jeu dans
// 10 secondes
expect(setTimeout).toHaveBeenCalledTimes(2);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 10000);
});
});
Pour le débogage ou toute autre raison, vous pouvez modifier la limite des temporisateurs qui seront exécutés avant de lever une erreur :
jest.useFakeTimers({timerLimit: 100});
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.
function timerGame(callback) {
console.log('Prêt .... partez !');
setTimeout(() => {
console.log("Le temps est écoulé -- arrêtez !");
callback && callback();
}, 1000);
}
module.exports = timerGame;
jest.useFakeTimers();
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()
.
Simulation sélective
Parfois, votre code peut nécessiter de ne pas écraser l'implémentation originale de l'une ou l'autre API. Si c'est le cas, vous pouvez utiliser l'option doNotFake
. Par exemple, voici comment vous pouvez fournir une fonction simulée personnalisée pour performance.mark()
dans l'environnement jsdom :
/**
* @jest-environment jsdom
*/
const mockPerformanceMark = jest.fn();
window.performance.mark = mockPerformanceMark;
test('permet la simulation de `performance.mark()`', () => {
jest.useFakeTimers({doNotFake: ['performance']});
expect(window.performance.mark).toBe(mockPerformanceMark);
});