摩卡/柴的Expect.to.throw不能捕获抛出的错误


257

我在让Chai的expect.to.thrownode.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.

Answers:


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.'));

执行此操作的方式将传递给call expect结果model.get('z')。但是要测试是否抛出了某些东西,您必须将一个函数传递给expect,该函数expect会自行调用。bind上面使用的方法创建了一个新函数,当调用该函数时,将model.get使用this设置为的值model和设置为的第一个参数调用'z'

bind可以在此处找到有关的很好的解释。


我确实通过了功能,不是吗?model实例具有一个名为get的函数,该函数在期望中传递/调用。
doremi 2014年

不,请参阅我在撰写评论时添加的说明。
路易斯

47
钱币。为什么文档(chaijs.com/api/bdd/#throw)没有演示这种bind的用法?似乎最常见的测试方案to.throw是测试函数中的特定条件,这需要使用无效的状态/参数来调用该函数。(为此,...为什么chaijs.com的深层链接实际上不是深层链接?)
ericsoco 2014年

当您通过一些不应该抛出的参数时,测试仍然是通过的。
Alexandros Spyropoulos 2014年

6
请注意,此功能(截至2017年9月)不适用于异步功能:请参见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); 由于没有notify方法,我无法尝试。
阿南德N

@AnandN如果我理解您的问题,这听起来像您只需要重构代码即可处理该错误。异步功能中未处理的错误在您的实际应用中也不会成为问题吗?
twiz 2015年

2
感谢twiz的回复。我们在集成环境中工作,using模块负责捕获异常。因此,问题是当我们尝试运行单元测试用例时。最后,我们使用下面的方法使其工作 catch (err) { expect(err).equal('Error message to be checked'); done(); }
Anand N

1
好的解决方案,除了在this要调用的函数内部使用时。然后.bind是正确的方法。
Rabbitco '16

@AnandN异步函数调用不抛出,它拒绝 s。供将来参考,chai-as-promise很好地处理了此问题。
user5532169

85

而且,如果您已经在使用ES6 / ES2015,则还可以使用箭头功能。它与使用常规匿名函数基本相同,但更短。

expect(() => model.get('z')).to.throw('Property does not exist in model schema.');

可能存在问题,因为箭头功能占据了周围的范围this
Eric Hodonsky

1
@Relic是的,非常正确。这也可能是箭头功能的一大优势。Arrow函数this从其创建的作用域中“继承” 。通常,这可能是一个优势,因为它避免了手动将bind函数传递给其this对象的需要。
Stijn de Witt

@StijndeWitt这不是优点或缺点,它是范围控制和故意的。它实际上是使用语法语法糖,bind并且始终将其绑定到this父作用域。我在评论中的意图仅是确保读者意识到潜在的陷阱。
Eric Hodonsky '19

1
@Relic是的,我同意你的看法。它可以发挥优势,并且可以成为使用箭头功能的充分理由。
Stijn de Witt

75

这个问题有很多重复项,包括未提及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);
  }
});

首先,这看起来会令人困惑。就像骑自行车一样,一旦点击,它就会永远“点击”。


13

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()解决方案更麻烦,但是可以帮助您指出Expect()需要一个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
});
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.