Gerenciamento de Tempo em Testes: Padrões Confiáveis para Async e Delays
O tempo é o assassino silencioso da confiabilidade dos testes. Um teste que passa localmente falha no CI. Uma suite que rodava em segundos agora leva minutos. Um componente que funciona perfeitamente apresenta falhas fantasmas porque timers e promises não cooperam.
Este artigo aborda padrões modernos de temporização em testes assíncronos e melhores práticas de fake timers para Jest, Vitest, React Testing Library, Playwright e Cypress—focando no que realmente funciona em 2025.
Principais Conclusões
- Use APIs de timer assíncronas (
advanceTimersByTimeAsync) em vez de variantes síncronas para prevenir deadlocks entre promises e timers - Configure timers com avanço automático ao combinar fake timers com utilitários assíncronos da Testing Library
- Sempre limpe fake timers no
afterEachpara prevenir poluição de testes e falhas em cascata - Em testes E2E, aguarde condições específicas em vez de usar delays arbitrários como
waitForTimeoutoucy.wait(ms)
Por Que o Tempo Quebra os Testes
Os testes falham em relação ao tempo por três razões: delays reais tornam a execução lenta, variações de temporização causam instabilidade, e fake timers conflitam com promises.
O event loop do JavaScript processa macrotasks (setTimeout, setInterval) e microtasks (callbacks de Promise) em uma ordem específica. Fake timers interceptam macrotasks mas não lidam automaticamente com microtasks. Essa incompatibilidade causa a maioria das falhas de testes relacionadas a timers.
Jest Async Timers: A Abordagem Moderna
A implementação atual de fake timers do Jest usa @sinonjs/fake-timers internamente. A chave para testes assíncronos confiáveis é entender quais APIs usar em conjunto.
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
test('debounced search triggers after delay', async () => {
render(<SearchInput />);
await userEvent.type(screen.getByRole('textbox'), 'query');
await jest.advanceTimersByTimeAsync(300);
expect(mockSearch).toHaveBeenCalledWith('query');
});
O detalhe crítico: use advanceTimersByTimeAsync em vez de advanceTimersByTime. A variante assíncrona libera microtasks entre execuções de timer, prevenindo o deadlock entre promise e timer que causa testes travados.
Para animações, o Jest fornece jest.advanceTimersToNextFrame() para avançar através de callbacks requestAnimationFrame sem esperar pela temporização real dos frames.
Vitest Fake Timers: Integração com Sinon
O Vitest compartilha a mesma base @sinonjs/fake-timers, fazendo com que os fake timers do Vitest se comportem de forma similar ao Jest com pequenas diferenças na API.
import { vi } from 'vitest';
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
test('toast disappears after timeout', async () => {
render(<Toast message="Saved" />);
await vi.advanceTimersByTimeAsync(3000);
expect(screen.queryByText('Saved')).not.toBeInTheDocument();
});
Ambos os frameworks suportam timers com avanço automático, que avança o tempo automaticamente quando o event loop ficaria ocioso. Isso elimina o gerenciamento manual de ticks para a maioria dos testes.
Discover how at OpenReplay.com.
Coordenação com React Testing Library
A interação entre fake timers e o waitFor da Testing Library causa confusão frequente. Quando fake timers estão ativos, waitFor faz polling mas o tempo não avança—criando loops infinitos.
A solução: configure fake timers para avançar automaticamente durante esperas assíncronas.
jest.useFakeTimers({ advanceTimers: true });
Isso instrui o Jest a avançar os timers quando promises estão pendentes, permitindo que queries waitFor e findBy funcionem naturalmente com fake timers.
Para user-event, certifique-se de estar usando a API atual. A biblioteca gerencia sua própria temporização internamente e se coordena com fake timers quando configurada corretamente.
Controle de Tempo em E2E: Playwright e Cypress
A API page.clock do Playwright fornece controle preciso de tempo em contextos de navegador:
await page.clock.install({ time: new Date('2025-01-15') });
await page.clock.fastForward(5000);
O Cypress oferece cy.clock() e cy.tick() para controle similar. Ambas as abordagens são preferíveis a waitForTimeout ou cy.wait(ms), que introduzem delays reais e instabilidade.
Armadilhas Comuns Que Causam Instabilidade
Vazamento de timers entre testes: Sempre restaure timers reais no afterEach. Fake timers vazados causam falhas em cascata em testes subsequentes.
Conflitos com bibliotecas de terceiros: Bibliotecas como frameworks de animação ou utilitários de polling agendam seus próprios timers. Estes podem não respeitar sua instalação de fake timers, causando comportamento inesperado.
Ordenação de microtask/macrotask: Código que mistura setTimeout com Promise.resolve() se comporta diferentemente sob fake timers. Use APIs de timer assíncronas consistentemente.
Considerações sobre a API Temporal: Bases de código mais recentes usando Temporal em vez de Date precisam de estratégias de mock separadas. Abstraia sua fonte de tempo para tornar os testes independentes de framework.
Conclusão
Prefira APIs de timer assíncronas (advanceTimersByTimeAsync) em vez de variantes síncronas. Configure avanço automático ao usar utilitários assíncronos da Testing Library. Evite sleeps arbitrários e waitForTimeout em testes E2E—aguarde condições específicas. Sempre limpe fake timers para prevenir poluição de testes.
O objetivo são testes que rodam rápido, passam consistentemente e não codificam detalhes de implementação de temporização. Fake timers com avanço automático te levam na maior parte do caminho. Para todo o resto, coordenação assíncrona explícita supera delays arbitrários sempre.
Perguntas Frequentes
Os testes travam quando fake timers bloqueiam macrotasks enquanto promises aguardam microtasks para resolver. Use métodos de timer assíncronos como advanceTimersByTimeAsync em vez de advanceTimersByTime. As variantes assíncronas liberam microtasks entre execuções de timer, quebrando o deadlock entre promises e timers.
Configure o Jest com a opção advanceTimers habilitada chamando jest.useFakeTimers({ advanceTimers: true }). Isso instrui o Jest a avançar automaticamente o tempo quando promises estão pendentes, permitindo que queries waitFor e findBy façam polling corretamente sem criar loops infinitos.
Use fake timers via page.clock do Playwright ou cy.clock e cy.tick do Cypress. Delays reais com waitForTimeout ou cy.wait introduzem instabilidade porque a velocidade de execução varia entre ambientes. Em vez disso, aguarde condições específicas da UI ou use avanço controlado de tempo.
Ambientes de CI têm recursos variáveis de CPU e memória, fazendo com que testes dependentes de temporização se comportem inconsistentemente. Substitua delays reais por fake timers, use asserções assíncronas que aguardam condições em vez de durações fixas, e garanta limpeza adequada de timers entre testes para prevenir poluição.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.