Aller au contenu principal
Version : 29.4

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 !

info

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().

timerGame.js
function timerGame(callback) {
console.log('PrĂȘt .... partez !');
setTimeout(() => {
console.log("Le temps est Ă©coulĂ© -- arrĂȘtez !");
callback && callback();
}, 1000);
}

module.exports = timerGame;
__tests__/timerGame-test.js
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('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 Ă©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 :

infiniteTimerGame.js
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;
__tests__/infiniteTimerGame-test.js
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);

// 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);
});
});
remarque

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.

timerGame.js
function timerGame(callback) {
console.log('PrĂȘt .... partez !');
setTimeout(() => {
console.log("Le temps est Ă©coulĂ© -- arrĂȘtez !");
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);
});

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

Selective Faking​

Sometimes your code may require to avoid overwriting the original implementation of one or another API. If that is the case, you can use doNotFake option. For example, here is how you could provide a custom mock function for performance.mark() in jsdom environment:

/**
* @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);
});