Jest: тестирование асинхронного кода

Jest поддерживает несколько способов тестирования асинхронного кода: async/await, .resolves/.rejects матчеры, callback с done, и фиктивные таймеры для setTimeout/setInterval.

Зачем нужно

Асинхронный код — основа современного JS: fetch-запросы, работа с БД, таймеры, очереди событий. Если не обработать асинхронность корректно, Jest завершит тест до разрешения Promise и не поймает ошибки. Знание паттернов async-тестирования обязательно для любого frontend и backend разработчика.

Где используется

  • Тестирование API-сервисов и HTTP-клиентов
  • Тестирование Redux thunk, saga, React Query
  • Проверка поведения с задержками (debounce, retry)
  • Тестирование WebSocket, потоков, event emitter

Основной контент

async/await — основной подход

test('получает данные пользователя', async () => {
  const user = await fetchUser(1);
  expect(user.name).toBe('Alice');
});

test('выбрасывает ошибку при 404', async () => {
  await expect(fetchUser(999)).rejects.toThrow('Not found');
});

.resolves / .rejects матчеры

test('Promise разрешается с нужным значением', () => {
  return expect(fetchUser(1)).resolves.toEqual({ id: 1, name: 'Alice' });
});

test('Promise отклоняется', () => {
  return expect(fetchUser(0)).rejects.toThrow('Invalid id');
});
// Важно: return обязателен — без него тест пройдёт не дождавшись Promise

Callback с done

// Старый стиль — предпочитай async/await
test('callback вызывается с данными', (done) => {
  fetchUserCb(1, (err, user) => {
    expect(err).toBeNull();
    expect(user.name).toBe('Alice');
    done; // сообщает Jest что тест завершён
  });
});

Фиктивные таймеры

jest.useFakeTimers;

test('вызывает callback через 1 секунду', () => {
  const callback = jest.fn;
  setTimeout(callback, 1000);

  expect(callback).not.toHaveBeenCalled();
  jest.advanceTimersByTime(1000);
  expect(callback).toHaveBeenCalledTimes(1);
});

afterEach(() => {
  jest.useRealTimers; // восстанавливаем реальные таймеры
});

Гарантия выполнения assertions

test('обрабатывает успешный ответ', async () => {
  expect.assertions(2); // ожидаем ровно 2 вызова expect
  try {
    const data = await fetchUser(1);
    expect(data.id).toBe(1);
    expect(data.name).toBe('Alice');
  } catch (e) {
    // если попадаем сюда — assertions не выполнятся и тест упадёт
  }
});

Частые ошибки

  • Нет await или return — тест завершается до разрешения Promise и проходит даже если Promise отклонился с ошибкой
  • Нет done в callback-тесте — Jest завершает тест немедленно, не дожидаясь callback
  • Реальные таймеры в тестах — тест с setTimeout(fn, 5000) занимает 5 секунд; используй jest.useFakeTimers
  • Не перехвачен отклонённый Promise — без rejects.toThrow или try/catch ошибка теряется

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

Ресурсы