Mocha / Chai expect.to.throw not catching thrown errors
Solution 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.
Solution 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.');
Solution 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.');
Solution 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:
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./);
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.
Solution 5
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
Related videos on Youtube
doremi
Updated on May 25, 2020Comments
-
doremi almost 4 years
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.
-
doremi over 10 yearsI did pass a function didn't I?
model
instance has a function called get which I passed/called in expect. -
Louis over 10 yearsNope, see the explanation I've added while you were writing your comment.
-
ericsoco over 9 yearsOof. Why don't the docs (chaijs.com/api/bdd/#throw) demonstrate this usage of bind? Seems like the most common testing scenario for
to.throw
is testing a particular condition within a function, which requires calling that function with the invalid state/arguments. (For that matter....why don't chaijs.com's deeplinks actually deeplink?) -
Alexandros Spyropoulos over 9 yearsWhen you pass some parameters that shouldn't throw, the test is still a pass though.
-
Louis over 9 years@AlexandrosSpyropoulos It's not been my experience so please give an example of a case that illustrates what you mean.
-
Alexandros Spyropoulos over 9 yearsThe first two filter pass, that means the the method runs as expected, the third case though (pass a valid parameter and do not throw an error) fails with... AssertionError: expected [Function] to not throw an error but 'RangeError: Value out of bounds!' was thrown That means that when the throw test executes the function, it always throws.
-
Louis over 9 years@AlexandrosSpyropoulos You are misusing
bind
. See in my answer how I havemodel.get.bind(model, ...
You needabs.set.bind(abs, ...
You need to pass the object instance as the first argument. It is the first argument tobind
that sets the value ofthis
inside the function. -
Alexandros Spyropoulos over 9 yearsOk got it, thank you so much... so what it basically does, is that we pass a version of the abs.set with preset context.
-
Anand N over 9 yearsThis is not working for asynchronous function calls. Suppose model.get is async that returns promise. However it throws an error. If I try the above approach, it is "Timing out" as we have to notify "done" to mocha. At the same time, I can't try
expect(function(){ model.get('z'); }).to.throw('Property does not exist in model schema.').notify(done);
As there is no notify method. -
twiz about 9 years@AnandN If I understand your problem, this sounds like you just need to refactor your code to handle the error. Wont the unhandled error in the async function be a problem in your actual app as well?
-
Anand N about 9 yearsThanks twiz for your reply. We are working in an integrated environment, the using module takes care of catching the exceptions. So, the problem is when we try to run unit test cases. Finally we used the below approach to get it working
catch (err) { expect(err).equal('Error message to be checked'); done(); }
-
jbarreiros over 8 yearsIf your test runner uses phantomjs, note that phantomjs v1 does not support "bind". Upgrade to v2.
-
rabbitco over 7 yearsGood solution except when you are using
this
inside the function to be called. Then.bind
is the right way to go. -
ChrisV over 6 yearsNote this won't (as of Sep 2017) work for async functions: see github.com/chaijs/chai/issues/882#issuecomment-322131680 and associated discussion.
-
Eric Hodonsky over 5 yearsThere CAN BE a problem with this because arrow functions take their surrounding scope for
this
-
Geek Stocks over 5 yearsOctober 2018: still a great answer!
-
Stijn de Witt over 5 years@Relic Yes, very true. This can also be a big advantage of arrow functions. Arrow functions 'inherit'
this
from the scope they where created in. Often this can be an advantage, as it avoid the need forbind
ing functions to theirthis
object manually. -
Eric Hodonsky over 5 years@StijndeWitt this is not an advantage or disadvantage, it's scope control and intentional. It's actually syntax sugar for using
bind
and always bind's tothis
of the parent scope. My intention in the comment was only to ensure readers were aware of a potential pit fall. -
Stijn de Witt over 5 years@Relic Yes I agree with you. It can be used to an advantage and can be a good reason to use an arrow function.
-
il0v3d0g almost 5 yearsThank you @ChrisV for your comment! I was able to solve my case by reading your comment and going to the link!
-
relief.melone almost 5 yearsThis is how I do it as well. I find, that the ES6 implementation is by far the most readable one
-
zypA13510 about 4 years@AnandN Asynchronous function call does not throw, it rejects. For future reference, chai-as-promised handles this quite nicely.