Vai al contenuto principale
{ "message": "Versione: 29.7", "description": "" }

Simulazione dei Timer

Traduzione Beta Non Ufficiale

Questa pagina è stata tradotta da PageTurner AI (beta). Non ufficialmente approvata dal progetto. Hai trovato un errore? Segnala problema →

Le funzioni native dei timer (come setTimeout(), setInterval(), clearTimeout(), clearInterval()) sono poco adatte per un ambiente di testing poiché dipendono dallo scorrere del tempo reale. Jest può sostituire i timer con funzioni che ti permettono di controllare il passaggio del tempo. Grande Giove!

info

Consulta anche la documentazione delle API Fake Timers.

Attivare i Timer Fittizi

Nell'esempio seguente attiviamo i timer fittizi chiamando jest.useFakeTimers(). Questa operazione sostituisce l'implementazione originale di setTimeout() e altre funzioni timer. I timer possono essere ripristinati al loro comportamento normale con jest.useRealTimers().

timerGame.js
function timerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up -- stop!");
callback && callback();
}, 1000);
}

module.exports = timerGame;
__tests__/timerGame-test.js
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);
});

Eseguire Tutti i Timer

Un altro test che potremmo voler scrivere per questo modulo è uno che verifichi che il callback venga chiamato dopo 1 secondo. Per farlo, utilizzeremo le API di controllo timer di Jest per avanzare rapidamente il tempo durante il 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);
});

Eseguire i Timer Pendenti

Esistono anche scenari in cui potresti avere un timer ricorsivo, cioè un timer che imposta un nuovo timer nel proprio callback. In questi casi, eseguire tutti i timer creerebbe un ciclo infinito, generando l'errore: "Interruzione dopo l'esecuzione di 100000 timer, rilevato un ciclo infinito!"

Se questo è il tuo caso, usare jest.runOnlyPendingTimers() risolverà il problema:

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

Per debug o altri motivi puoi modificare il limite di timer eseguiti prima di generare un errore:

jest.useFakeTimers({timerLimit: 100});

Avanzare i Timer di un Tempo Specifico

Un'altra possibilità è usare jest.advanceTimersByTime(msToRun). Quando viene chiamata questa API, tutti i timer avanzano di msToRun millisecondi. Tutte le "macro-attività" pendenti in coda (via setTimeout() o setInterval()) che dovrebbero essere eseguite in questo intervallo verranno eseguite. Inoltre, se queste macro-attività ne schedulano di nuove eseguibili nello stesso intervallo, queste verranno eseguite finché non ci sono più macro-attività nella coda da eseguire entro msToRun millisecondi.

timerGame.js
function timerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up -- stop!");
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);
});

Infine, in alcuni test può essere utile poter cancellare tutti i timer pendenti. Per questo abbiamo jest.clearAllTimers().

Simulazione Selettiva

A volte il tuo codice potrebbe richiedere di evitare la sovrascrittura dell'implementazione originale di una specifica API. In tal caso, puoi usare l'opzione doNotFake. Ad esempio, ecco come potresti fornire una funzione mock personalizzata per performance.mark() in ambiente 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);
});