Simuladores de Temporizadores
Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
Las funciones nativas de temporizador (como setTimeout(), setInterval(), clearTimeout(), clearInterval()) no son ideales para entornos de pruebas porque dependen del tiempo real transcurrido. Jest puede reemplazar estos temporizadores con funciones que te permiten controlar el paso del tiempo. ¡Gran Scott!
Consulta también la documentación de la API de Temporizadores Falsos.
Habilitar temporizadores falsos
En el siguiente ejemplo habilitamos temporizadores falsos llamando a jest.useFakeTimers(). Esto reemplaza la implementación original de setTimeout() y otras funciones de temporizador. Los temporizadores pueden restaurarse a su comportamiento normal con 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);
});
Ejecutar todos los temporizadores
Otra prueba que podríamos escribir para este módulo es una que verifique que el callback se ejecute después de 1 segundo. Para lograrlo, usaremos las APIs de control de temporizadores de Jest para adelantar el tiempo durante la prueba:
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);
});
Ejecutar temporizadores pendientes
También existen escenarios con temporizadores recursivos, donde un temporizador programa un nuevo temporizador en su propio callback. En estos casos, ejecutar todos los temporizadores crearía un bucle infinito, generando el error: "Aborting after running 100000 timers, assuming an infinite loop!"
Si ese es tu caso, usar jest.runOnlyPendingTimers() resolverá el problema:
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);
});
});
Para depuración u otros motivos, puedes cambiar el límite de temporizadores ejecutados antes de generar un error:
jest.useFakeTimers({timerLimit: 100});
Avanzar temporizadores por tiempo
Otra opción es usar jest.advanceTimersByTime(msToRun). Al llamar esta API, todos los temporizadores avanzan msToRun milisegundos. Todas las "macro-tareas" pendientes encoladas vía setTimeout() o setInterval() que debían ejecutarse en este periodo se ejecutarán. Además, si esas macro-tareas programan nuevas macro-tareas dentro del mismo marco temporal, estas también se ejecutarán hasta que no queden más macro-tareas en la cola dentro de los msToRun milisegundos.
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);
});
Finalmente, en algunas pruebas puede ser útil borrar todos los temporizadores pendientes. Para esto existe jest.clearAllTimers().
Avanzar temporizadores al siguiente cuadro
En aplicaciones, es común programar trabajo dentro de un cuadro de animación (con requestAnimationFrame). Ofrecemos el método conveniente jest.advanceTimersToNextFrame() para avanzar los temporizadores los milisegundos necesarios para ejecutar todos los cuadros de animación activamente programados.
Para simulación temporal, los cuadros de animación se ejecutan cada 16ms (equivalente a ~60 cuadros por segundo) tras iniciar el reloj. Cuando programas un callback en un cuadro de animación (con requestAnimationFrame(callback)), el callback se llamará cuando el reloj avance 16ms. jest.advanceTimersToNextFrame() adelanta el reloj justo hasta el siguiente incremento de 16ms. Si el reloj ya avanzó 6ms desde que se programó el callback del cuadro de animación, el reloj avanzará 10ms adicionales.
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);
});
Falsificación selectiva
A veces tu código requiere evitar sobrescribir la implementación original de ciertas APIs. En esos casos, puedes usar la opción doNotFake. Por ejemplo, así proporcionarías una función simulada personalizada para performance.mark() en entorno 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);
});