Ir para o conteúdo principal
Versão: Próximo

Simulação de Temporizadores

Tradução Beta Não Oficial

Esta página foi traduzida por PageTurner AI (beta). Não é oficialmente endossada pelo projeto. Encontrou um erro? Reportar problema →

As funções nativas de temporizador (como setTimeout(), setInterval(), clearTimeout(), clearInterval()) não são ideais para ambientes de teste porque dependem da passagem de tempo real. O Jest pode substituir esses temporizadores por funções que permitem controlar o fluxo do tempo. Great Scott!

"message": "informação"

Veja também a documentação da API de Temporizadores Falsos.

Ativar Temporizadores Falsos

No exemplo a seguir, ativamos temporizadores falsos chamando jest.useFakeTimers(). Isso substitui a implementação original do setTimeout() e outras funções de temporizador. Os temporizadores podem ser restaurados ao comportamento normal com 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);
});

Executar Todos os Temporizadores

Outro teste que podemos criar para este módulo verifica se o callback é chamado após 1 segundo. Para isso, usaremos as APIs de controle de temporizador do Jest para avançar rapidamente o tempo durante o teste:

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

Executar Temporizadores Pendentes

Há cenários com temporizadores recursivos – onde um temporizador configura outro temporizador em seu próprio callback. Nesses casos, executar todos os temporizadores criaria um loop infinito, gerando o erro: "Abortando após executar 100000 temporizadores, presumindo loop infinito!"

Se for seu caso, usar jest.runOnlyPendingTimers() resolverá o 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);
});
});
"message": "nota"

Para depuração ou outros motivos, você pode alterar o limite de temporizadores executados antes do erro:

jest.useFakeTimers({timerLimit: 100});

Avançar Temporizadores por Tempo

Outra opção é usar jest.advanceTimersByTime(msToRun). Quando chamada, essa API avança todos os temporizadores em msToRun milissegundos. Todas as "macro-tarefas" pendentes enfileiradas via setTimeout() ou setInterval() serão executadas. Se essas macro-tarefas agendarem novas que devam executar no mesmo período, elas também serão processadas até que a fila esteja vazia.

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

Por fim, em alguns testes pode ser útil limpar todos os temporizadores pendentes. Para isso, temos jest.clearAllTimers().

Avançar para o Próximo Frame de Animação

Em aplicações, é comum agendar trabalho dentro de frames de animação (com requestAnimationFrame). Disponibilizamos o método conveniente jest.advanceTimersToNextFrame() para avançar os temporizadores o suficiente e executar todos os frames de animação agendados.

Para fins de simulação temporal, os frames de animação são executados a cada 16ms (mapeando aproximadamente 60 quadros por segundo) após o relógio iniciar. Ao agendar um callback num frame de animação (com requestAnimationFrame(callback)), o callback será chamado quando o relógio avançar 16ms. jest.advanceTimersToNextFrame() avança o relógio exatamente o suficiente para atingir o próximo incremento de 16ms. Se o relógio já avançou 6ms desde que um callback de frame de animação foi agendado, então o relógio avançará 10ms.

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

Simulação Seletiva

Às vezes seu código precisa evitar substituir implementações originais de APIs específicas. Nesses casos, use a opção doNotFake. Por exemplo, veja como fornecer uma função mock personalizada para performance.mark() em 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);
});