Mocha / Chai expect.to.throw 没有捕捉到抛出的错误

IT技术 javascript node.js mocha.js chai
2021-02-08 22:49:33

我在让 Chaiexpect.to.throw为我的 node.js 应用程序进行测试时遇到问题测试在抛出的错误上一直失败,但是如果我将测试用例包装在 try 和 catch 中,并对捕获的错误进行断言,它就可以工作。

难道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.
6个回答

您必须将函数传递给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使用thisset 的值model和第一个参数设置为'z'

bind可以在这里找到一个很好的解释

钱币。为什么文档(chaijs.com/api/bdd/#throw)不演示 bind 的这种用法?似乎最常见的测试场景to.throw是测试函数内的特定条件,这需要使用无效的状态/参数调用该函数。(就此而言……为什么 chaijs.com 的深层链接实际上不是深层链接?)
2021-03-11 22:49:33
请注意,这不适用于异步函数(截至 2017 年 9 月):请参阅github.com/chaijs/chai/issues/882#issuecomment-322131680和相关讨论。
2021-03-13 22:49:33
我确实传递了一个函数,不是吗?model实例有一个名为 get 的函数,我在期望中传递/调用了它。
2021-03-26 22:49:33
不,请参阅我在您撰写评论时添加的解释。
2021-03-27 22:49:33
谢谢@ChrisV 的评论!通过阅读您的评论并转到链接,我能够解决我的问题!
2021-04-09 22:49:33

正如这个答案所说,您也可以将您的代码包装在一个匿名函数中,如下所示:

expect(function(){
    model.get('z');
}).to.throw('Property does not exist in model schema.');
感谢 twiz 的回复。我们在一个集成环境中工作, using module负责捕获异常。所以,问题是当我们尝试运行单元测试用例时。最后我们使用下面的方法让它工作 catch (err) { expect(err).equal('Error message to be checked'); done(); }
2021-03-14 22:49:33
这不适用于异步函数调用。假设 model.get 是返回Promise的异步。但是它会引发错误。如果我尝试上述方法,则是“超时”,因为我们必须将“完成”通知给 mocha。同时,我无法尝试,expect(function(){ model.get('z'); }).to.throw('Property does not exist in model schema.').notify(done); 因为没有notify方法。
2021-03-16 22:49:33
@AnandN 异步函数调用不会throw,它会拒绝s。为了将来参考,chai-as-promised很好地处理了这个问题。
2021-03-16 22:49:33
@AnandN如果我理解你的问题,这听起来你只需要重构你的代码来处理错误。异步函数中未处理的错误在您的实际应用中也会成为问题吗?
2021-03-18 22:49:33
很好的解决方案,除非您this在要调用的函数内部使用然后.bind才是正确的出路。
2021-03-30 22:49:33

如果你已经在使用 ES6/ES2015,那么你也可以使用箭头函数。它与使用普通匿名函数基本相同,但更短。

expect(() => model.get('z')).to.throw('Property does not exist in model schema.');
@StijndeWitt 这不是优点或缺点,而是范围控制和有意的。它实际上是用于使用bind并始终绑定到this父作用域的语法糖我在评论中的意图只是为了确保读者意识到潜在的陷阱。
2021-03-18 22:49:33
这可能存在问题,因为箭头函数将其周围的范围用于 this
2021-03-19 22:49:33
@Relic 是的,我同意你的看法。它可以用于一个优势,并且可以成为使用箭头函数的一个很好的理由。
2021-03-27 22:49:33
@Relic 是的,非常真实。这也是箭头函数的一大优势。箭头函数this从创建它们的作用域“继承” 。这通常是一个优势,因为它避免了手动将bind函数添加到this对象的需要。
2021-04-01 22:49:33

这个问题有很多很多重复,包括没有提到 Chai 断言库的问题。以下是收集到的基础知识:

断言必须调用函数,而不是立即求值。

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

乍一看,这可能令人困惑。就像骑自行车一样,一旦发出咔嗒声,它就会永远“咔哒”一声。

文档中的示例... ;)

因为你依赖于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 实现是迄今为止最易读的
2021-03-26 22:49:33