Исключения и отрицание not

Урок показывает, как тестировать выбрасываемые ошибки и как инвертировать любой матчер.

toThrow проверяет, что вызов выбрасывает исключение, а .not инвертирует смысл любого матчера.

Тестирование исключений: toThrow

Когда функция должна бросать ошибку при неверном вводе, это тоже поведение, и его надо проверять. Важная тонкость: в expect нужно передать функцию-обёртку, а не результат вызова — иначе ошибка вылетит раньше, чем Jest её перехватит.

function withdraw(balance, amount) {
  if (amount > balance) {
    throw new Error('Недостаточно средств');
  }
  return balance - amount;
}

// ПРАВИЛЬНО: оборачиваем в стрелочную функцию
expect(() => withdraw(100, 200)).toThrow();
expect(() => withdraw(100, 200)).toThrow('Недостаточно средств');
expect(() => withdraw(100, 200)).toThrow(/средств/);

// НЕПРАВИЛЬНО: ошибка выбросится до того, как Jest успеет её поймать
// expect(withdraw(100, 200)).toThrow();

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

Почему нужна обёртка — на чистом JS

Воспроизведём механику «обернуть вызов, чтобы поймать ошибку» вживую:

function withdraw(balance, amount) {
  if (amount > balance) throw new Error('Недостаточно средств');
  return balance - amount;
}

function expectToThrow(fn, name) {
  try {
    fn();
    console.log('\u2717 FAILED: ' + name + ' (ошибки не было)');
  } catch (e) {
    console.log('\u2713 passed: ' + name + ' \u2192 ' + e.message);
  }
}

expectToThrow(() => withdraw(100, 200), 'снятие больше баланса бросает ошибку');
expectToThrow(() => withdraw(100, 50), 'снятие в пределах баланса бросает ошибку');

Вывод:

✓ passed: снятие больше баланса бросает ошибку → Недостаточно средств
✗ FAILED: снятие в пределах баланса бросает ошибку (ошибки не было)

Здесь видно, зачем нужна функция-обёртка: try/catch вызывает её сам и ловит исключение. Jest toThrow делает ровно это под капотом.

Отрицание: .not

Любой матчер можно инвертировать, вставив .not перед ним:

expect(sum(2, 2)).not.toBe(5);
expect(['a', 'b']).not.toContain('c');
expect(() => withdraw(100, 50)).not.toThrow();
expect(user.name).not.toBeNull();

.not читается как «не должно быть»: «expect 2+2 not to be 5».

Когда применять

  • toThrow — для проверки валидации, защитных условий, обработки ошибок.
  • .not — когда важно отсутствие значения/ошибки.

С .not не злоупотребляйте: позитивная проверка («должно быть X») обычно точнее негативной («не должно быть Y»), потому что Y может быть бесконечно много.

Итог

  • toThrow проверяет выброс ошибки; вызов оборачивают в функцию.
  • Можно уточнять ошибку подстрокой, регуляркой или классом.
  • .not инвертирует любой матчер.
  • Позитивные проверки обычно надёжнее негативных.
Проверьте себя
1. Почему в expect для toThrow передают функцию-обёртку, а не результат вызова?
AТак короче
BИначе ошибка выбросится до того, как Jest сможет её перехватить
CtoThrow требует строку
DФункции работают быстрее
2. Что делает .not в выражении expect(x).not.toBe(5)?
AПропускает тест
BИнвертирует матчер: проверяет, что x НЕ равен 5
CДелает тест асинхронным
DУдваивает проверку
3. Какой аргумент НЕ принимает toThrow?
AПодстроку сообщения
BРегулярное выражение
CКласс ошибки
DГотовый объект Promise
Поддержать проект