Мокко / Чай ожидают. К броску не ловят брошенные ошибки

259

У меня проблемы с тем, чтобы Chai's expect.to.throwработал в тесте для моего приложения node.js. Тест продолжает давать сбой на выданную ошибку, но если я оберну тестовый пример в попытку и поймать и утверждать на обнаруженную ошибку, это работает.

Не expect.to.throwработает, как я думаю, что должно или что-то?

it('should throw an error if you try to get an undefined property', function (done) {
  var params = { a: 'test', b: 'test', c: 'test' };
  var model = new TestModel(MOCK_REQUEST, params);

  // neither of these work
  expect(model.get('z')).to.throw('Property does not exist in model schema.');
  expect(model.get('z')).to.throw(new Error('Property does not exist in model schema.'));

  // this works
  try { 
    model.get('z'); 
  }
  catch(err) {
    expect(err).to.eql(new Error('Property does not exist in model schema.'));
  }

  done();
});

Провал:

19 passing (25ms)
  1 failing

  1) Model Base should throw an error if you try to get an undefined property:
     Error: Property does not exist in model schema.
до Ре Ми
источник

Ответы:

340

Вы должны передать функцию expect. Как это:

expect(model.get.bind(model, 'z')).to.throw('Property does not exist in model schema.');
expect(model.get.bind(model, 'z')).to.throw(new Error('Property does not exist in model schema.'));

То , как вы делаете это, вы передаете в expectв результате вызова model.get('z'). Но чтобы проверить, выброшено ли что-то, вам нужно передать функцию expect, которая expectбудет вызывать сама себя. bindМетод , используемый выше , создает новую функцию , которая при вызове будет вызывать model.getс thisнабором к значению modelи первому набору аргументов к 'z'.

Хорошее объяснение bindможно найти здесь .

Луис
источник
Я прошел функцию, не так ли? modelЭкземпляр имеет функцию с именем get, которую я передал / вызвал в ожидаемом.
дореми
Нет, посмотрите объяснение, которое я добавил, когда вы писали свой комментарий.
Луи
47
Уф. Почему документы ( chaijs.com/api/bdd/#throw ) не демонстрируют такое использование bind? Похоже, что наиболее распространенный сценарий to.throwтестирования - это тестирование определенного условия внутри функции, которое требует вызова этой функции с недопустимым состоянием / аргументами. (В этом отношении .... почему не глубокие ссылки chaijs.com на самом деле Deeplink?)
ericsoco
Когда вы передаете некоторые параметры, которые не должны выдаваться, тест все же проходит.
Александрос Спиропулос
6
Обратите внимание, что это не будет (по состоянию на сентябрь 2017 г.) для асинхронных функций: см. Github.com/chaijs/chai/issues/882#issuecomment-322131680 и соответствующее обсуждение.
ChrisV
175

Как говорится в этом ответе , вы также можете просто обернуть свой код в анонимную функцию, например:

expect(function(){
    model.get('z');
}).to.throw('Property does not exist in model schema.');
twiz
источник
7
Это не работает для асинхронных вызовов функций. Предположим, что model.get является асинхронным и возвращает обещание. Однако это выдает ошибку. Если я попробую описанный выше подход, это «тайм-аут», поскольку мы должны уведомить «готово» к мокко. В то же время я не могу попробовать, так expect(function(){ model.get('z'); }).to.throw('Property does not exist in model schema.').notify(done); как нет способа уведомления.
Ананд N
@AnandN Если я понимаю вашу проблему, это звучит так, как будто вам просто нужно реорганизовать свой код для обработки ошибки. Разве необработанная ошибка в асинхронной функции будет проблемой в вашем реальном приложении?
Twiz
2
Спасибо тебе за ответ. Мы работаем в интегрированной среде, модуль using заботится о перехвате исключений. Итак, проблема в том, когда мы пытаемся запустить модульные тесты. Наконец, мы использовали приведенный ниже подход, чтобы заставить его работать catch (err) { expect(err).equal('Error message to be checked'); done(); }
Anand N
1
Хорошее решение, кроме случаев, когда вы используете thisвнутри вызываемой функции. Тогда .bindэто правильный путь.
rabbitco
Вызов функции @AnandN Асинхронный не кидать , он отвергает с. Для дальнейшего использования Chai-as-обещанный справляется с этим довольно хорошо.
user5532169
85

И если вы уже используете ES6 / ES2015, то вы также можете использовать функцию стрелки. По сути, это то же самое, что и обычная анонимная функция, но короче.

expect(() => model.get('z')).to.throw('Property does not exist in model schema.');
Даниэль Т.
источник
С этим МОЖЕТ БЫТЬ проблема, потому что функции стрелок принимают окружающие возможностиthis
Эрик Ходонски,
1
@Relic Да, очень верно. Это также может быть большим преимуществом функций стрелок. Функции со стрелками «наследуют» thisот области, в которой они были созданы. Зачастую это может быть преимуществом, поскольку позволяет избежать необходимости вручную вводить bindфункции для своего thisобъекта.
Стейн де Витт
@StijndeWitt это не преимущество или недостаток, это контроль объема и намеренный. На самом деле это синтаксический сахар для использования bindи всегда привязки к thisродительской области видимости. Мое намерение в комментарии было только для того, чтобы читатели знали о возможном падении ямы.
Эрик Ходонский
1
@Relic Да, я согласен с тобой. Это может быть использовано для преимущества и может быть хорошей причиной для использования функции стрелки.
Стейн де Витт
75

Этот вопрос имеет много-много дубликатов, включая вопросы, не упоминающие библиотеку утверждений Чай. Вот основы, собранные вместе:

Утверждение должно вызывать функцию, а не вычислять ее немедленно.

assert.throws(x.y.z);      
   // FAIL.  x.y.z throws an exception, which immediately exits the
   // enclosing block, so assert.throw() not called.
assert.throws(()=>x.y.z);  
   // assert.throw() is called with a function, which only throws
   // when assert.throw executes the function.
assert.throws(function () { x.y.z });   
   // if you cannot use ES6 at work
function badReference() { x.y.z }; assert.throws(badReference);  
   // for the verbose
assert.throws(()=>model.get(z));  
   // the specific example given.
homegrownAssertThrows(model.get, z);
   //  a style common in Python, but not in JavaScript

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

Узел

  assert.throws(() => x.y.z);
  assert.throws(() => x.y.z, ReferenceError);
  assert.throws(() => x.y.z, ReferenceError, /is not defined/);
  assert.throws(() => x.y.z, /is not defined/);
  assert.doesNotThrow(() => 42);
  assert.throws(() => x.y.z, Error);
  assert.throws(() => model.get.z, /Property does not exist in model schema./)

Должен

  should.throws(() => x.y.z);
  should.throws(() => x.y.z, ReferenceError);
  should.throws(() => x.y.z, ReferenceError, /is not defined/);
  should.throws(() => x.y.z, /is not defined/);
  should.doesNotThrow(() => 42);
  should.throws(() => x.y.z, Error);
  should.throws(() => model.get.z, /Property does not exist in model schema./)

Чай Ожидать

  expect(() => x.y.z).to.throw();
  expect(() => x.y.z).to.throw(ReferenceError);
  expect(() => x.y.z).to.throw(ReferenceError, /is not defined/);
  expect(() => x.y.z).to.throw(/is not defined/);
  expect(() => 42).not.to.throw();
  expect(() => x.y.z).to.throw(Error);
  expect(() => model.get.z).to.throw(/Property does not exist in model schema./);

Вы должны обрабатывать исключения, которые «избегают» теста

it('should handle escaped errors', function () {
  try {
    expect(() => x.y.z).not.to.throw(RangeError);
  } catch (err) {
    expect(err).to.be.a(ReferenceError);
  }
});

На первый взгляд это может показаться странным. Как и при езде на велосипеде, он просто «щелкает» вечно, когда щелкает.

Чарльз Мерриам
источник
16

примеры из док ...;)

потому что вы полагаетесь на thisконтекст:

  • который теряется, когда функция вызывается .throw
  • у него нет возможности узнать, что это должно быть

Вы должны использовать один из следующих вариантов:

  • обернуть вызов метода или функции внутри другой функции
  • связать контекст

    // wrap the method or function call inside of another function
    expect(function () { cat.meow(); }).to.throw();  // Function expression
    expect(() => cat.meow()).to.throw();             // ES6 arrow function
    
    // bind the context
    expect(cat.meow.bind(cat)).to.throw();           // Bind
Михал Мики Янковский
источник
Вот как я это делаю. Я нахожу, что реализация ES6 является наиболее читаемой
рельеф. Melone
1

Еще одна возможная реализация, более громоздкая, чем решение .bind (), но та, которая помогает понять, что ожидаемо () требует функции, которая предоставляет thisконтекст для покрытой функции, вы можете использовать call(), например,

expect(function() {model.get.call(model, 'z');}).to.throw('...');

SeanOlson
источник
0

Я нашел хороший способ обойти это:

// The test, BDD style
it ("unsupported site", () => {
    The.function(myFunc)
    .with.arguments({url:"https://www.ebay.com/"})
    .should.throw(/unsupported/);
});


// The function that does the magic: (lang:TypeScript)
export const The = {
    'function': (func:Function) => ({
        'with': ({
            'arguments': function (...args:any) {
                return () => func(...args);
            }
        })
    })
};

Это намного более читабельно, чем моя старая версия:

it ("unsupported site", () => {
    const args = {url:"https://www.ebay.com/"}; //Arrange
    function check_unsupported_site() { myFunc(args) } //Act
    check_unsupported_site.should.throw(/unsupported/) //Assert
});
Дани-Br
источник