Тестирование асинхронного кода и фейковые таймеры

↑ 2.2 Тестирование: основы · ← Предыдущая

Обязательно~4 мин чтенияУровень: middle

Зачем это на собесе

Тесты 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 даёт ложно-зелёный результат.

Практика

  1. Протестируйте debounce, retry с backoff и поллинг с фейковыми таймерами.
  2. Зафиксируйте текущую дату в тесте форматирования.
  3. Найдите тест, забывший await, с помощью expect.assertions.

Вопросы с ответами

Связанные темы