Exception matchers

Jasmine предоставляет мощные инструменты для тестирования исключений и ошибок, возникающих в коде. В JavaScript обработка исключений осуществляется через конструкции try...catch или посредством механизма throw. В контексте тестирования важно убедиться, что функции выбрасывают ожидаемые ошибки при определённых условиях. Для этого в Jasmine используются exception matchers.


Проверка, что функция выбрасывает ошибку

Основной matcher для проверки исключений — toThrow(). Он позволяет убедиться, что вызов функции приводит к выбросу исключения любого типа:

describe('toThrow example', () => {
  it('выбрасывает ошибку', () => {
    function problematicFunction() {
      throw new Error('Ошибка выполнения');
    }

    expect(problematicFunction).toThrow();
  });
});

Ключевой момент: функция должна передаваться в expect без вызова, то есть без скобок. Если написать expect(problematicFunction()), исключение произойдёт до выполнения expect, и тест не сможет его поймать.


Проверка конкретного типа ошибки

Для более точной проверки используется toThrowError(). Этот matcher позволяет сопоставлять тип ошибки, текст сообщения или регулярное выражение:

describe('toThrowError examples', () => {
  function throwTypeError() {
    throw new TypeError('Некорректный тип данных');
  }

  it('выбрасывает TypeError', () => {
    expect(throwTypeError).toThrowError(TypeError);
  });

  it('сообщение содержит "тип"', () => {
    expect(throwTypeError).toThrowError(/тип/);
  });

  it('сообщение точно совпадает', () => {
    expect(throwTypeError).toThrowError('Некорректный тип данных');
  });
});

Особенности toThrowError:

  • Можно передавать класс ошибки (TypeError, ReferenceError) для проверки типа.
  • Можно передавать строку или регулярное выражение для проверки текста сообщения.
  • Можно комбинировать тип ошибки и сообщение одновременно: toThrowError(TypeError, 'сообщение').

Проверка асинхронных функций

Jasmine поддерживает асинхронное тестирование с использованием async/await или промисов. Исключения в асинхронном коде требуют другой конструкции. Прямой вызов toThrow() не работает, поскольку ошибка выбрасывается вне синхронного контекста.

Пример с async/await:

describe('Async exception test', () => {
  async function asyncFunction() {
    throw new Error('Асинхронная ошибка');
  }

  it('ловит исключение в async функции', async () => {
    await expectAsync(asyncFunction()).toBeRejectedWithError('Асинхронная ошибка');
  });
});

Здесь используется специальный matcher для асинхронного кодаtoBeRejectedWithError, который проверяет промис на отклонение с конкретной ошибкой.


Практические рекомендации

  1. Не вызывать функцию в expect напрямую. Передача вызова (func()) приведёт к ошибке до выполнения matcher. Всегда передавать саму функцию: expect(func).
  2. Выбирать правильный matcher в зависимости от ситуации. Для синхронных функций — toThrow, toThrowError; для промисов и asyncexpectAsync(...).toBeRejectedWithError.
  3. Использовать регулярные выражения для проверки части текста сообщения, если оно может меняться в разных средах.
  4. Разделять тесты по типу ошибки. Один тест должен проверять конкретное исключение, чтобы ошибки были однозначными и легко диагностируемыми.

Комбинирование с другими matchers

Иногда полезно проверять состояние объекта или возвращаемое значение до или после выброса исключения:

describe('Exception with state check', () => {
  let counter = 0;

  function incrementAndThrow() {
    counter++;
    throw new Error('Ошибка счётчика');
  }

  it('увеличивает счётчик перед исключением', () => {
    expect(incrementAndThrow).toThrowError('Ошибка счётчика');
    expect(counter).toBe(1);
  });
});

Такой подход обеспечивает не только проверку ошибки, но и правильность побочных эффектов функции.


Заключение по ключевым моментам matcher’ов

  • toThrow() — проверка любого исключения.
  • toThrowError() — проверка типа ошибки и сообщения.
  • expectAsync(...).toBeRejectedWithError() — проверка ошибок в асинхронных функциях.
  • Всегда передавать функцию, а не её вызов.
  • Регулярные выражения повышают гибкость проверки сообщений.
  • Разделение тестов по конкретным исключениям улучшает читаемость и отладку.

Exception matchers в Jasmine позволяют создавать точные и надёжные тесты для обработки ошибок, что критично при разработке стабильного и предсказуемого JavaScript-кода.