Mocha / Chai очікують.to.throw не ловити викинуті помилки


257

У мене виникають проблеми з тим, 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.

Відповіді:


339

Ви повинні передати функцію 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 ) не демонструють цього використання прив'язки? Схоже, найпоширеніший сценарій тестування для to.throwтестування певної умови в межах функції, яка вимагає викликати цю функцію недійсним станом / аргументами. (З цього приводу .... чому б не переходити посилання на chaijs.com насправді у посилання?)
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.');

7
Це не працює для асинхронних викликів функцій. Припустимо, model.get - це асинхроніка, яка повертає обіцянку. Однак це видає помилку. Якщо я спробую описати вищезазначений підхід, це "Визначення часу", оскільки ми маємо сповістити "зроблено" для мокко. У той же час я не можу спробувати, expect(function(){ model.get('z'); }).to.throw('Property does not exist in model schema.').notify(done); оскільки немає способу сповіщення.
Ананд N

@AnandN Якщо я розумію вашу проблему, це виглядає так, що вам просто потрібно перефактурувати код, щоб вирішити помилку. Не хочете, щоб непоправлена ​​помилка функції асинхронізації була проблемою і у вашому фактичному додатку?
twiz

2
Дякую твіз за вашу відповідь. Ми працюємо в інтегрованому середовищі, використовуючи модуль дбає про вилучення винятків. Отже, проблема полягає в тому, що ми намагаємося запускати одиничні тестові справи. Нарешті ми застосували нижченаведений підхід, щоб змусити його працювати catch (err) { expect(err).equal('Error message to be checked'); done(); }
Anand N

1
Гарне рішення, за винятком випадків, коли ви використовуєте thisвсередині функції, яку потрібно викликати. Тоді .bindправильний шлях.
rabbitco

@AnandN Асинхронний виклик функції не кидає , він відхиляє s. Для подальшої довідки chai, як обіцяно, справляється з цим досить добре.
користувач5532169

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);
  }
});

Спочатку це може виглядати заплутано. Як і на велосипеді, він просто натискає назавжди, як тільки клацає.


14

приклади з doc ...;)

тому що ви покладаєтесь на 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 на сьогодні є найбільш читаною
relief.melone

1

Ще однією можливою реалізацією, більш громіздкою, ніж рішення .bind (), але те, що допомагає зробити точку, яка очікує (), вимагає функції, яка забезпечує thisконтекст для функції, що охоплюється, ви можете використовувати call(), наприклад,

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


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
});
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.