Mocha / Chai expect.to.throw not catching thrown e

2018-12-31 16:52发布

问题:

I\'m having issues getting Chai\'s expect.to.throw to work in a test for my node.js app. The test keeps failing on the thrown error, but If I wrap the test case in try and catch and assert on the caught error, it works.

Does expect.to.throw not work like I think it should or something?

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

The failure:

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.

回答1:

You have to pass a function to expect. Like this:

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

The way you are doing it, you are passing to expect the result of calling model.get(\'z\'). But to test whether something is thrown, you have to pass a function to expect, which expect will call itself. The bind method used above creates a new function which when called will call model.get with this set to the value of model and the first argument set to \'z\'.

A good explanation of bind can be found here.



回答2:

As this answer says, you can also just wrap your code in an anonymous function like this:

expect(function(){
    model.get(\'z\');
}).to.throw(\'Property does not exist in model schema.\');


回答3:

And if you are already using ES6/ES2015 then you can also use an arrow function. It is basically the same as using a normal anonymous function but shorter.

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


回答4:

This question has many, many duplicates, including questions not mentioning the Chai assertion library. Here are the basics collected together:

The assertion must call the function, instead of it evaluating immediately.

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

You can check for specific errors using any assertion library:

Node

  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

  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./)

Chai Expect

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

You must handle exceptions that \'escape\' the test

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

This can look confusing at first. Like riding a bike, it just \'clicks\' forever once it clicks.



回答5:

examples from doc... ;)

because you relies on this context:

  • which is lost when the function is invoked by .throw
  • there’s no way for it to know what this is supposed to be

you have to use one of these options:

  • wrap the method or function call inside of another function
  • bind the context

    // 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
    


回答6:

One other possible implementation, more cumbersome than the .bind() solution, but one that helps to make the point that expect() requires a function that provides a this context to the covered function, you can use a call(), e.g.,

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