Обработка ошибок в асинхронном коде

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

Обработка ошибок с помощью try...catch

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

Пример использования try...catch в тестах:

test('Асинхронная функция выбрасывает ошибку', async () => {
  try {
    await someAsyncFunction();
  } catch (error) {
    expect(error).toBeInstanceOf(Error);
    expect(error.message).toBe('Expected error message');
  }
});

В данном примере мы ожидаем, что someAsyncFunction() выбросит ошибку, и с помощью catch перехватываем её. Далее в блоке catch проверяем тип ошибки и сообщение.

Использование rejects для ожидания ошибок

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

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

test('Асинхронная функция отклоняет промис с ошибкой', async () => {
  await expect(someAsyncFunction()).rejects.toThrow('Expected error message');
});

Здесь метод rejects.toThrow() позволяет проверить, что промис был отклонён и содержал ошибку с ожидаемым сообщением. Это намного более компактный и читаемый способ, чем использование try...catch.

Работа с асинхронными функциями, возвращающими промисы

Для асинхронных функций, которые возвращают промисы, можно использовать async/await или метод then в Jest для корректной обработки ошибок.

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

test('Промис отклоняется с ошибкой', () => {
  return someAsyncFunction().then(() => {
    throw new Error('Expected error');
  }).catch((error) => {
    expect(error).toBeInstanceOf(Error);
    expect(error.message).toBe('Expected error');
  });
});

Здесь важно правильно обрабатывать как успешное завершение промиса, так и отклонение. В случае отклонения промиса выполнение переходит в блок catch, где ошибка проверяется через expect.

Проверка ошибок в колбэках

В случае использования асинхронных операций, использующих колбэки, например, в старых API или при работе с внешними библиотеками, можно проверить ошибки с помощью done().

Пример с колбэком:

test('Колбэк вызывает ошибку', (done) => {
  someAsyncFunctionWithCallback((error, result) => {
    expect(error).toBeInstanceOf(Error);
    expect(error.message).toBe('Expected callback error');
    done();
  });
});

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

Обработка ошибок в асинхронных функциях с тайм-аутами

Для асинхронных операций, которые могут зависать или выполняться слишком долго, следует использовать тайм-ауты. Jest позволяет установить тайм-аут для теста, что помогает избежать “зависших” тестов.

Пример с установкой тайм-аута:

test('Асинхронная операция с тайм-аутом', async () => {
  jest.setTimeout(10000);  // Устанавливаем тайм-аут в 10 секунд
  await expect(someAsyncFunction()).rejects.toThrow('Timeout error');
});

В этом примере установлено ограничение на время выполнения теста. Если операция не завершится в указанное время, тест будет провален.

Проверка ошибок с использованием async и await в цепочках

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

Пример:

test('Цепочка асинхронных операций с ошибкой', async () => {
  await expect(
    someAsyncFunction()
      .then(result => anotherAsyncFunction(result))
      .catch(error => {
        throw new Error('Chain error');
      })
  ).rejects.toThrow('Chain error');
});

Здесь ошибка может возникнуть как в первом, так и во втором асинхронном действии. Метод catch используется для перехвата и повторного выбрасывания ошибки, которая затем проверяется с помощью rejects.toThrow().

Применение beforeEach и afterEach для подготовки и очистки ошибок

Иногда перед выполнением теста нужно подготовить определённое состояние системы или перехватить глобальные ошибки. Это можно сделать через хук beforeEach или afterEach. Эти хуки позволяют организовать настройку теста и его очистку.

Пример использования хука для перехвата ошибок:

beforeEach(() => {
  global.console.error = jest.fn();  // Перехватываем вызовы console.error
});

test('Проверка ошибки с перехватом вывода', async () => {
  await someAsyncFunctionThatLogsError();
  expect(console.error).toHaveBeenCalledWith('Expected error message');
});

В этом примере мы используем хук beforeEach для перехвата ошибок, выводимых через console.error. После этого проверяется, был ли вызов с ожидаемым сообщением.

Использование собственных обработчиков ошибок

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

Пример:

class CustomError extends Error {
  constructor(message) {
    super(message);
    this.name = 'CustomError';
  }
}

test('Проверка кастомной ошибки', async () => {
  try {
    await someAsyncFunctionThatThrowsCustomError();
  } catch (error) {
    expect(error).toBeInstanceOf(CustomError);
    expect(error.message).toBe('This is a custom error');
  }
});

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

Заключение

Обработка ошибок в асинхронных тестах является важной частью тестирования. Jest предоставляет различные способы для работы с ошибками: использование try...catch, rejects, done(), настройка тайм-аутов и проверка кастомных ошибок. Правильное использование этих инструментов позволяет повысить надёжность тестов и обеспечивать корректное поведение приложения в случае возникновения ошибок.