Как написать тест, который ожидает, что ошибка будет выдана в Жасмин?


490

Я пытаюсь написать тест для Jasmine Test Framework, который ожидает ошибку. В данный момент я использую интеграцию Jasmine Node.js из GitHub .

В моем модуле Node у меня есть следующий код:

throw new Error("Parsing is not possible");

Теперь я пытаюсь написать тест, который ожидает эту ошибку:

describe('my suite...', function() {
    [..]
    it('should not parse foo', function() {
    [..]
        expect(parser.parse(raw)).toThrow(new Error("Parsing is not possible"));
    });
});

Я попробовал также Error()и некоторые другие варианты и просто не могу понять, как заставить это работать.


4
Чтобы передать аргументы тестируемой функции без использования анонимной функции, попробуйте Function.bind: stackoverflow.com/a/13233194/294855
Даниал Айтекин

Ответы:


802

Вы должны передавать функцию в expect(...)вызов. Код у вас здесь:

// incorrect:
expect(parser.parse(raw)).toThrow(new Error("Parsing is not possible"));

пытается на самом деле вызвать parser.parse(raw) в попытке передать результат в expect(...),

Попробуйте вместо этого использовать анонимную функцию:

expect( function(){ parser.parse(raw); } ).toThrow(new Error("Parsing is not possible"));

28
Если вам также не нужно передавать аргументы, вы также можете просто передать функцию, чтобы ожидать:expect(parser.parse).toThrow(...)
SubmittedDenied

60
Полезный совет: вы можете просто позвонить expect(blah).toThrow(). Отсутствие аргументов означает проверку, чтобы увидеть, что он бросает вообще. Соответствие строк не требуется. См. Также: stackoverflow.com/a/9525172/1804678
Джесс

1
На мой взгляд, это более очевидно в отношении цели теста при добавлении в анонимную функцию. Кроме того, он остается неизменным среди всех тестов, когда, например, вам необходимо передать параметры целевой функции, чтобы она сработала.
Beez

10
@SubmittedDenied: это вообще не работает! Если parser.parseиспользуется this, передача его без контекста приведет к неожиданным результатам. Вы могли бы пройти parser.parse.bind(parser), но, честно говоря ... анонимная функция была бы более элегантной.
Мельвенс

2
@LanceKind извините за некро, но причина, по которой вы должны передать функцию, состоит в том, что значение будет оценено и сгенерирует исключение еще до того, как оно будет передано в ожидаемое значение.
1gLassitude

68

Ты используешь:

expect(fn).toThrow(e)

Но если вы посмотрите на комментарий функции (ожидается строка):

294 /**
295  * Matcher that checks that the expected exception was thrown by the actual.
296  *
297  * @param {String} expected
298  */
299 jasmine.Matchers.prototype.toThrow = function(expected) {

Я полагаю, вы должны написать это так (используя лямбда-анонимную функцию):

expect(function() { parser.parse(raw); } ).toThrow("Parsing is not possible");

Это подтверждается в следующем примере:

expect(function () {throw new Error("Parsing is not possible")}).toThrow("Parsing is not possible");

Дуглас Крокфорд настоятельно рекомендует этот подход вместо использования «throw new Error ()» (способ создания прототипа):

throw {
   name: "Error",
   message: "Parsing is not possible"
}

3
На самом деле просмотр кода toThrow с радостью примет либо объект исключения, либо / строку. Проверьте звонки, которые он делает к ожидаемому сообщению, например.
Пит Ходжсон

1
Это похоже на строку как побочный эффект строки, не имеющей свойства сообщения
mpapis

1
Большое спасибо, что работал. Я все еще принял ответ Пита, потому что его ответ сделал мне более ясным, что я должен использовать лямбду. Еще +1 :-) Спасибо!
echox

16
Если вы выбрасываете объект, а не ошибку (как в вашем примере внизу), то вы не получите трассировку стека в браузерах, которые его поддерживают.
kybernetikos

2
@kybernetikos на удивление, не совсем верно; вы все равно получите трассировку стека, напечатанную в консоли Chrome, если выбросите не- Error( jsfiddle.net/k1mxey8j ). Однако у вашего брошенного объекта, разумеется, не будет .stackсвойства, которое может быть важно, если вы хотите настроить автоматическое создание отчетов об ошибках.
Марк Эмери

24

Более элегантное решение, чем создание анонимной функции, единственной целью которой является обтекание другой, является использование bindфункции es5 . Функция связывания создает новую функцию, для которой при вызове ее thisключевое слово устанавливается на предоставленное значение с заданной последовательностью аргументов, предшествующей любому предоставленному при вызове новой функции.

Вместо:

expect(function () { parser.parse(raw, config); } ).toThrow("Parsing is not possible");

Рассматривать:

expect(parser.parse.bind(parser, raw, config)).toThrow("Parsing is not possible");

Синтаксис bind позволяет вам тестировать функции с разными thisзначениями, и, на мой взгляд, делает тест более читабельным. Смотрите также: https://stackoverflow.com/a/13233194/1248889


23

Я заменяю сопоставление toThrow от Jasmine следующим: оно позволяет сопоставить свойство name исключения или его свойство message. Для меня это облегчает написание тестов и делает их менее хрупкими, поскольку я могу делать следующее:

throw {
   name: "NoActionProvided",
   message: "Please specify an 'action' property when configuring the action map."
}

и затем проверьте следующее:

expect (function () {
   .. do something
}).toThrow ("NoActionProvided");

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

Это замена toThrow, которая позволяет это:

jasmine.Matchers.prototype.toThrow = function(expected) {
  var result = false;
  var exception;
  if (typeof this.actual != 'function') {
    throw new Error('Actual is not a function');
  }
  try {
    this.actual();
  } catch (e) {
    exception = e;
  }
  if (exception) {
      result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected) || this.env.equals_(exception.name, expected));
  }

  var not = this.isNot ? "not " : "";

  this.message = function() {
    if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
      return ["Expected function " + not + "to throw", expected ? expected.name || expected.message || expected : " an exception", ", but it threw", exception.name || exception.message || exception].join(' ');
    } else {
      return "Expected function to throw an exception.";
    }
  };

  return result;
};

4
Хороший подход, но является ли {name: '...', message: '...'} надлежащим объектом Error в JavaScript?
Марк

1
Хороший комментарий @Marc. Вы правы, свойство name не является стандартным. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… но так ли это неправильно?
Джесс

4
@Джейк! Я нашел лучший способ !!!! Вы можете просто позвонить expect(blah).toThrow(). Отсутствие аргументов означает проверку, чтобы увидеть, что он бросает на всех. Соответствие строк не требуется. См. Также: stackoverflow.com/a/9525172/1804678
Джесс

5
Спасибо, Джесс, это правда, но тогда это может привести к какой-то другой ошибке, такой как TypeError, и мой тест пройдет неправильно, маскируя настоящую ошибку.
Джейк

4
Теперь вы также можете использовать RegEx в качестве аргумента для toThrow ().
Тони О'Хаган

21

Как упоминалось ранее, необходимо передать функцию, так toThrowкак это функция, которую вы описываете в своем тесте: «Я ожидаю, что эта функция выдает x»

expect(() => parser.parse(raw))
  .toThrow(new Error('Parsing is not possible'));

При использовании Jasmine-Matchers, вы также можете использовать один из следующих вариантов, когда они соответствуют ситуации;

// I just want to know that an error was
// thrown and nothing more about it
expect(() => parser.parse(raw))
  .toThrowAnyError();

или

// I just want to know that an error of 
// a given type was thrown and nothing more
expect(() => parser.parse(raw))
  .toThrowErrorOfType(TypeError);

3
Это expect(foo).toThrowError(TypeError);в Жасмин 2.5: jasmine.github.io/2.5/introduction
Бенни Нойгебауэр,

9

Я знаю, что это больше кода, но вы также можете сделать:

try
   do something
   @fail Error("should send a Exception")
 catch e
   expect(e.name).toBe "BLA_ERROR"
   expect(e.message).toBe 'Message'

Мне, как правило, нравится аспект «самодокументирования» ... это делает очень очевидным, что вы тестируете состояние ошибки
модулем


3

Для тех, кто все еще может столкнуться с этой проблемой, для меня опубликованное решение не сработало, и оно продолжало выдавать эту ошибку: Error: Expected function to throw an exception. позже я понял, что функция, которую я ожидал выдать ошибку, была асинхронной функцией и ожидала обещания быть отклоненным и затем выдать ошибку, и это то, что я делал в своем коде:

throw new Error('REQUEST ID NOT FOUND');

и вот что я сделал в моем тесте, и это сработало:

it('Test should throw error if request not found', willResolve(() => {
         const promise = service.getRequestStatus('request-id');
                return expectToReject(promise).then((err) => {
                    expect(err.message).toEqual('REQUEST NOT FOUND');
                });
            }));

Спасибо за это. Я был очень смущен, но ваш комментарий имеет смысл. Я исправил проблему, используя новый expectAsync jasmine.github.io/api/3.3/async-matchers.html
Бенджамин
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.