Manejo del Tiempo en Pruebas: Patrones Confiables para Async y Delays
El tiempo es el asesino silencioso de la confiabilidad de las pruebas. Una prueba que pasa localmente falla en CI. Una suite que se ejecutaba en segundos ahora toma minutos. Un componente que funciona perfectamente muestra fallos fantasma porque los temporizadores y las promesas no cooperan.
Este artículo cubre patrones modernos de temporización en pruebas asíncronas y mejores prácticas de temporizadores falsos para Jest, Vitest, React Testing Library, Playwright y Cypress—enfocándose en lo que realmente funciona en 2025.
Puntos Clave
- Usa APIs de temporizadores asíncronos (
advanceTimersByTimeAsync) en lugar de variantes síncronas para prevenir bloqueos entre promesas y temporizadores - Configura temporizadores con avance automático cuando combines temporizadores falsos con utilidades asíncronas de Testing Library
- Siempre limpia los temporizadores falsos en
afterEachpara prevenir contaminación de pruebas y fallos en cascada - En pruebas E2E, espera condiciones específicas en lugar de usar delays arbitrarios como
waitForTimeoutocy.wait(ms)
Por Qué el Tiempo Rompe las Pruebas
Las pruebas fallan alrededor del tiempo por tres razones: los delays reales ralentizan la ejecución, las variaciones de temporización causan inestabilidad, y los temporizadores falsos entran en conflicto con las promesas.
El event loop de JavaScript procesa macrotareas (setTimeout, setInterval) y microtareas (callbacks de Promise) en un orden específico. Los temporizadores falsos interceptan macrotareas pero no manejan automáticamente las microtareas. Esta discrepancia causa la mayoría de los fallos relacionados con temporizadores en las pruebas.
Temporizadores Asíncronos de Jest: El Enfoque Moderno
La implementación actual de temporizadores falsos de Jest usa @sinonjs/fake-timers internamente. La clave para pruebas asíncronas confiables es entender qué APIs usar juntas.
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');
});
El detalle crítico: usa advanceTimersByTimeAsync en lugar de advanceTimersByTime. La variante asíncrona vacía las microtareas entre ejecuciones de temporizadores, previniendo el bloqueo entre promesas y temporizadores que causa pruebas colgadas.
Para animaciones, Jest proporciona jest.advanceTimersToNextFrame() para avanzar a través de callbacks de requestAnimationFrame sin esperar la temporización real de frames.
Temporizadores Falsos de Vitest: Integración con Sinon
Vitest comparte la misma base de @sinonjs/fake-timers, haciendo que los temporizadores falsos de Vitest se comporten de manera similar a Jest con diferencias menores en la 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 frameworks soportan temporizadores con avance automático, que avanza el tiempo automáticamente cuando el event loop de otro modo estaría inactivo. Esto elimina la gestión manual de ticks para la mayoría de las pruebas.
Discover how at OpenReplay.com.
Coordinación con React Testing Library
La interacción entre temporizadores falsos y waitFor de Testing Library causa confusión frecuente. Cuando los temporizadores falsos están activos, waitFor hace polling pero el tiempo no avanza—creando bucles infinitos.
La solución: configura los temporizadores falsos para que avancen automáticamente durante esperas asíncronas.
jest.useFakeTimers({ advanceTimers: true });
Esto le indica a Jest que avance los temporizadores cuando las promesas están pendientes, permitiendo que las queries waitFor y findBy funcionen naturalmente con temporizadores falsos.
Para user-event, asegúrate de estar usando la API actual. La biblioteca maneja su propia temporización internamente y se coordina con temporizadores falsos cuando se configura correctamente.
Control de Tiempo en E2E: Playwright y Cypress
La API page.clock de Playwright proporciona control preciso del tiempo en contextos de navegador:
await page.clock.install({ time: new Date('2025-01-15') });
await page.clock.fastForward(5000);
Cypress ofrece cy.clock() y cy.tick() para un control similar. Ambos enfoques son preferibles a waitForTimeout o cy.wait(ms), que introducen delays reales e inestabilidad.
Errores Comunes Que Causan Inestabilidad
Fugas de temporizadores entre pruebas: Siempre restaura los temporizadores reales en afterEach. Los temporizadores falsos filtrados causan fallos en cascada en pruebas subsecuentes.
Conflictos con bibliotecas de terceros: Bibliotecas como frameworks de animación o utilidades de polling programan sus propios temporizadores. Estos pueden no respetar tu instalación de temporizadores falsos, causando comportamiento inesperado.
Ordenamiento de microtareas/macrotareas: El código que mezcla setTimeout con Promise.resolve() se comporta diferente bajo temporizadores falsos. Usa APIs de temporizadores asíncronos de manera consistente.
Consideraciones de la API Temporal: Bases de código más nuevas que usan Temporal en lugar de Date necesitan estrategias de mocking separadas. Abstrae tu fuente de tiempo para hacer las pruebas independientes del framework.
Conclusión
Prefiere APIs de temporizadores asíncronos (advanceTimersByTimeAsync) sobre variantes síncronas. Configura el avance automático cuando uses utilidades asíncronas de Testing Library. Evita sleeps arbitrarios y waitForTimeout en pruebas E2E—espera condiciones específicas en su lugar. Siempre limpia los temporizadores falsos para prevenir contaminación de pruebas.
El objetivo son pruebas que se ejecuten rápido, pasen consistentemente y no codifiquen detalles de implementación de temporización. Los temporizadores falsos con avance automático te llevan la mayor parte del camino. Para todo lo demás, la coordinación asíncrona explícita supera los delays arbitrarios cada vez.
Preguntas Frecuentes
Las pruebas se cuelgan cuando los temporizadores falsos bloquean macrotareas mientras las promesas esperan que las microtareas se resuelvan. Usa métodos de temporizadores asíncronos como advanceTimersByTimeAsync en lugar de advanceTimersByTime. Las variantes asíncronas vacían las microtareas entre ejecuciones de temporizadores, rompiendo el bloqueo entre promesas y temporizadores.
Configura Jest con la opción advanceTimers habilitada llamando jest.useFakeTimers({ advanceTimers: true }). Esto le indica a Jest que avance automáticamente el tiempo cuando las promesas están pendientes, permitiendo que las queries waitFor y findBy hagan polling correctamente sin crear bucles infinitos.
Usa temporizadores falsos a través de page.clock de Playwright o cy.clock y cy.tick de Cypress. Los delays reales con waitForTimeout o cy.wait introducen inestabilidad porque la velocidad de ejecución varía entre entornos. En su lugar, espera condiciones específicas de UI o usa avance controlado del tiempo.
Los entornos de CI tienen recursos variables de CPU y memoria, causando que las pruebas dependientes de temporización se comporten inconsistentemente. Reemplaza delays reales con temporizadores falsos, usa aserciones asíncronas que esperen condiciones en lugar de duraciones fijas, y asegura la limpieza adecuada de temporizadores entre pruebas para prevenir contaminación.
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.