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

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.

7 Answers

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.

10

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

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

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.

0

examples from doc... ;)

because you rely 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
1

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

I have found a nice way around it:

// The test, BDD style
it ("unsupported site", () => { The.function(myFunc) .with.arguments({url:""}) .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's much more readable then my old version:

it ("unsupported site", () => { const args = {url:""}; //Arrange function check_unsupported_site() { myFunc(args) } //Act check_unsupported_site.should.throw(/unsupported/) //Assert
});

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

You Might Also Like