Тестирование асинхронного кода и фейковые таймеры
↑ 2.2 Тестирование: основы · ← Предыдущая
Зачем это на собесе
Тесты debounce, поллинга, запросов: как не использовать реальные задержки и не получить flaky-тесты.
Объяснение
Асинхронный тест: возвращайте промис или используйте async/await; иначе тест завершится до проверки.
it("загружает пользователя", async () => {
vi.mocked(api.getUser).mockResolvedValue({ id: 1, name: "A" });
await expect(loadUser(1)).resolves.toMatchObject({ name: "A" });
});
it("бросает при ошибке сети", async () => {
vi.mocked(api.getUser).mockRejectedValue(new Error("offline"));
await expect(loadUser(1)).rejects.toThrow("offline");
});Фейковые таймеры:
import { vi, beforeEach, afterEach } from "vitest";
beforeEach(() => vi.useFakeTimers());
afterEach(() => vi.useRealTimers());
it("debounce вызывает функцию один раз после паузы", () => {
const fn = vi.fn(); const d = debounce(fn, 300);
d(); d(); d();
expect(fn).not.toHaveBeenCalled();
vi.advanceTimersByTime(300);
expect(fn).toHaveBeenCalledTimes(1);
});
it("управляет временем и датой", async () => {
vi.setSystemTime(new Date("2025-01-01T00:00:00Z"));
expect(new Date().getFullYear()).toBe(2025);
await vi.advanceTimersByTimeAsync(1000); // выполняет и микрозадачи промисов
await vi.runAllTimersAsync();
});Методы: advanceTimersByTime, runAllTimers, runOnlyPendingTimers, advanceTimersToNextTimer, setSystemTime; асинхронные версии *Async нужны, когда таймеры и промисы переплетены.
Ожидание изменения состояния:
await vi.waitFor(() => expect(spy).toHaveBeenCalled()); // опрос до успеха или таймаута
await flushPromises(); // из @vue/test-utilsНюансы и подводные камни
- Реальные
setTimeoutв тестах замедляют набор и вызывают flaky. - С фейковыми таймерами промисы не «проматываются»: используйте
awaitи*Async. - Забытый
useRealTimersломает следующие тесты. - Не проверяйте порядок микрозадач через
setTimeout(0). - Тест без
awaitна асинхронныйexpectдаёт ложно-зелёный результат.
Практика
- Протестируйте
debounce,retryс backoff и поллинг с фейковыми таймерами. - Зафиксируйте текущую дату в тесте форматирования.
- Найдите тест, забывший
await, с помощьюexpect.assertions.
Вопросы с ответами
Как тестировать код с таймерами без ожидания?
vi.useFakeTimers()и ручное продвижение времени черезadvanceTimersByTime.
Почему тест прошёл, хотя проверка ложная?
Не дождались асинхронный
expect: тест завершился раньше, чем выполнилась проверка.