Асинхронные матчеры

Асинхронные операции являются неотъемлемой частью современного JavaScript-разработки. Методы работы с асинхронным кодом, такие как setTimeout, setInterval, Promise, async/await, используют временные задержки и внешние ресурсы, с которыми необходимо взаимодействовать во время тестирования. Для эффективного тестирования таких операций в Jest существуют специальные инструменты — асинхронные матчеры.

Асинхронные матчеры в Jest позволяют тестировать функциональность, которая работает с асинхронным кодом, синхронизируя выполнение тестов с завершением асинхронных операций. Это помогает избежать ложных положительных или отрицательных результатов тестов, связанных с задержками выполнения.

Основные асинхронные матчеры

resolves

Метод resolves используется для проверки, что промис был успешно разрешён, и для проверки его значения. Он применяется в сочетании с методами утверждения, такими как toBe, toEqual и другими, чтобы удостовериться, что асинхронный результат соответствует ожидаемому.

Пример использования:

test('Promise resolves to value', async () => {
  await expect(Promise.resolve(42)).resolves.toBe(42);
});

В данном примере проверяется, что промис, возвращающий значение 42, разрешается с этим значением.

rejects

Метод rejects аналогичен resolves, но используется для проверки отклонённых промисов. Он позволяет убедиться, что промис был отклонён с ожидаемой ошибкой или значением.

Пример использования:

test('Promise rejects with error', async () => {
  await expect(Promise.reject(new Error('Failed'))).rejects.toThrow('Failed');
});

Здесь проверяется, что промис отклоняется с ошибкой, содержащей сообщение ‘Failed’.

toBeResolved и toBeRejected

Это комбинации матчеров resolves и rejects, которые предоставляют альтернативный, более краткий синтаксис для проверок успешных или неудачных промисов.

Пример:

test('Promise resolves successfully', async () => {
  await expect(Promise.resolve(10)).toBeResolved();
});

test('Promise rejects with an error', async () => {
  await expect(Promise.reject(new Error('Something went wrong'))).toBeRejected();
});

Взаимодействие с async/await

Jest позволяет использовать async/await для удобного тестирования асинхронных операций. С помощью await можно “дожидаться” выполнения асинхронных операций в тестах, а expect помогает осуществлять проверки на эти результаты.

Пример использования:

test('Async function returns correct result', async () => {
  const result = await asyncFunction();
  expect(result).toBe('Success');
});

В этом примере асинхронная функция asyncFunction вызывается с помощью await, и результат её выполнения проверяется через expect.

Проверка времени выполнения с setTimeout и setInterval

Асинхронные операции, использующие setTimeout и setInterval, могут вызвать проблемы в тестах, если не учесть время, необходимое для их выполнения. Jest предоставляет методы, такие как jest.useFakeTimers() и jest.runAllTimers(), которые помогают манипулировать временем в тестах.

Пример с использованием фальшивых таймеров:

jest.useFakeTimers();

test('setTimeout works correctly', () => {
  const mockCallback = jest.fn();
  
  setTimeout(() => {
    mockCallback('Hello');
  }, 1000);

  expect(mockCallback).not.toHaveBeenCalled();
  
  jest.runAllTimers(); // Мануальное "проведение" времени

  expect(mockCallback).toHaveBeenCalledWith('Hello');
});

В данном тесте создаётся таймер с использованием setTimeout, но с помощью фальшивых таймеров Jest управление временем осуществляется вручную, что позволяет точно контролировать, когда будет вызван mockCallback.

Работа с async функциями и таймерами

Когда тестируем асинхронные функции, использующие таймеры, важно правильно синхронизировать ожидания. Использование jest.advanceTimersByTime и jest.runOnlyPendingTimers даёт возможность точно имитировать и управлять таймерами.

Пример:

jest.useFakeTimers();

test('Async function with setTimeout behaves as expected', async () => {
  const asyncFunctionWithTimer = () => new Promise((resolve) => {
    setTimeout(() => {
      resolve('done');
    }, 500);
  });

  const resultPromise = asyncFunctionWithTimer();
  
  jest.advanceTimersByTime(500);  // Имитируем прохождение времени
  
  await expect(resultPromise).resolves.toBe('done');
});

В этом примере асинхронная функция с таймером проверяется с помощью jest.advanceTimersByTime, чтобы искусственно ускорить выполнение теста и дождавшись разрешения промиса.

Применение done в старых версиях Jest

В старых версиях Jest для синхронизации асинхронных операций использовался коллбек done. С помощью done можно было явно указать, когда тест завершён, что позволяло избежать проблем с таймингами.

Пример:

test('Async test using done callback', (done) => {
  setTimeout(() => {
    expect(true).toBe(true);
    done();
  }, 1000);
});

Однако в современных версиях Jest рекомендуется использовать async/await вместо коллбека done, так как это более современный и предпочтительный подход.

Ловушки асинхронных ошибок

Когда тестируются промисы или асинхронные функции, важно помнить, что ошибки могут быть не пойманы в случае, если они не будут явным образом переданы в цепочку .catch() или использована конструкция try/catch в асинхронных функциях.

Пример ловушки ошибки:

test('Promise rejects with an error', async () => {
  await expect(Promise.reject(new Error('Error occurred'))).rejects.toThrow('Error occurred');
});

При таком подходе ошибка будет поймана, и тест завершится успешно, если она соответствует ожидаемому значению.

Заключение

Асинхронные матчеры Jest — мощные инструменты для тестирования асинхронных операций, таких как промисы, таймеры и асинхронные функции. Они позволяют точно синхронизировать выполнение тестов и гарантировать корректность работы асинхронного кода, исключая проблемы с временными задержками и непредсказуемым поведением. Использование таких матчеров, как resolves, rejects, а также возможность управления временем через фальшивые таймеры, обеспечивает точность и надёжность тестов в условиях асинхронной работы.