Тестирование async-кода

Урок показывает три способа тестировать асинхронный код в Jest и типичную ошибку с ним.

Главное правило: тест должен дождаться завершения асинхронной операции, иначе Jest посчитает его пройденным раньше времени.

Главная ловушка

Если в тесте есть промис, но вы его не дождались, Jest завершит тест до того, как проверка выполнится — и тест ложно «пройдёт». Поэтому асинхронность нужно явно ждать.

Способ 1: async/await

Самый читаемый вариант — пометить тест async и использовать await:

async function fetchUser(id) {
  return { id, name: 'Аня' }; // в реальности — запрос к API
}

test('возвращает пользователя по id', async () => {
  const user = await fetchUser(1);
  expect(user.name).toBe('Аня');
});

Способ 2: вернуть промис

Если вернуть промис из теста, Jest сам дождётся его. Удобны матчеры resolves и rejects:

test('промис резолвится в имя', () => {
  return expect(fetchUser(1)).resolves.toEqual({ id: 1, name: 'Аня' });
});

test('запрос несуществующего бросает', () => {
  return expect(fetchUser(-1)).rejects.toThrow('Not found');
});

Не забывайте return (или await) — без него промис не дождётся.

Способ 3: колбэк done

Для старого колбэк-API есть параметр done: тест считается завершённым, только когда вы его вызовете:

test('колбэк вызывается с данными', (done) => {
  loadData((err, data) => {
    expect(data).toBe('ok');
    done(); // сообщаем Jest: тест завершён
  });
});

Сегодня async/await предпочтительнее, но done полезен для событийных и колбэк-интерфейсов.

Почему нужно ждать — на чистом JS

Покажем, как await меняет порядок выполнения:

function fetchUser(id) {
  return Promise.resolve({ id, name: 'Аня' });
}

async function run() {
  console.log('1: до await');
  const user = await fetchUser(1);
  console.log('2: получили', user.name);
  console.log('3: проверка пройдена:', user.name === 'Аня');
}

run();
console.log('синхронный код после run()');

Вывод:

1: до await
синхронный код после run()
2: получили Аня
3: проверка пройдена: true

Обратите внимание: строка «синхронный код после run()» печатается до получения данных. Если бы тест не дождался промиса, проверка (строка 3) не успела бы выполниться — именно поэтому в Jest обязательно await/return.

Итог

  • Асинхронный тест обязан дождаться операции, иначе пройдёт ложно.
  • async/await — самый читаемый способ.
  • resolves/rejects проверяют исход промиса; не забывайте return/await.
  • done — для колбэк- и событийного API.
Проверьте себя
1. Что произойдёт, если в тесте не дождаться промиса?
AJest упадёт с синтаксической ошибкой
BТест может ложно пройти, завершившись раньше проверки
CПромис выполнится дважды
DТест зависнет навсегда
2. Для чего нужны матчеры resolves и rejects?
AЧтобы ускорить промисы
BЧтобы заменить async
CЧтобы проверить, во что разрешается или чем отклоняется промис
DЧтобы пропустить асинхронный тест
3. Когда уместен параметр done?
AВсегда вместо await
BТолько для синхронного кода
CДля отключения теста
DДля колбэк- и событийного API, где нет промиса