309

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

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.
4

7 回答 7

385

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

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

于 2014-02-05T19:59:23.873 回答
205

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

expect(function(){
    model.get('z');
}).to.throw('Property does not exist in model schema.');
于 2014-03-12T01:26:33.783 回答
106

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

expect(() => model.get('z')).to.throw('Property does not exist in model schema.');
于 2016-04-08T13:20:05.433 回答
90

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

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

于 2017-04-16T18:01:19.363 回答
25

来自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
    
于 2018-03-18T11:50:02.717 回答
1

另一种可能的实现,比 .bind() 解决方案更麻烦,但有助于说明 expect() 需要一个为this覆盖函数提供上下文的函数,您可以使用 a call(),例如,

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

于 2018-10-08T17:26:53.120 回答
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
});
于 2020-05-21T21:49:51.937 回答