Mocha await/async handling (expected) errors - javascript

Some time ago I was curious on how to execute async tests using Mocha and those tests to expect an error as a result (see Mocha async test handle errors)
Now I tried the same for the newer version of nodejs using await/async. Just a simple case but as I need to wrap the code in a try / catch block, things get out of hand.
After long hours of testing I ended up with a solution but it doesn't look good.
So I'm looking for a better way to do this.
The test looks like this:
it('myTest', async function () {
// THIS WORKS => CODE OPTION 1:
// this works, it uses promises but not await/async
return somePromiseFunction()
.then(() => Promise.reject(new Error('Expected method to reject.')))
.catch(err => assert.ok(err instanceof Error, 'This should be an error!'));
// THIS WORKS => CODE OPTION 2:
// this works, it uses await/async
let forceFail = false;
try {
await somePromiseFunction();
forceFail = true;
} catch (err) {}
if (forceFail) assert.ok(false, 'Expected method to reject.');
// WONT WORK => CODE OPTION 3:
try {
await somePromiseFunction();
assert.ok(false, 'you shouln\'t be here');
} catch (err) {
assert.ok(err instanceof Error, 'This should be an error!');
}
});
Both option 1 and 2 work. Option 1 uses classic Promise.then.catch syntax, and that's ok. Option 2 was the only way for me to make things work but is very complex to understand / maintain. It relies on a sort of global variable and handling states, and it just doesn't look good.
There is an option 3 in the code, but it does not work. Something like that would be easy to read but it does not work at all, not the first assert nor the second one.
If you remove the try / catch block then it won't work either.
Thanks.

In the third option, err would always be undefined since you're catching the exception as e, not as err. And the assert would always fail because undefined is not an instance of Error.

I found a way, but you'll need to upgrade to node version 10. I was using 8, but this new feature included in assert solves my problem in an elegant way.
Instead of trying this:
// WONT WORK => CODE OPTION 3:
try {
await somePromiseFunction();
assert.ok(false, 'you shouln\'t be here');
} catch (err) {
assert.ok(err instanceof Error, 'This should be an error!');
}
Do this:
// this works!
await assert.rejects(
async () => somePromiseFunction(),
Error
);
You can also verify the type of error being thrown, but in my case Error will suffice.
EDIT: async () => was not really necessary, I ended up with a one line assert (second parameter is optional, but I'd rather keep it):
await assert.rejects(somePromiseFunction(), Error);

Related

Jest/SuperTest how to correctly expect Socket Hangup across a set of promises?

I have a test that says "After approx X concurrent connections, I should see socket hangups since my service will stop answering someone hammering me."
This works pretty well after a lot of painful trial and error, but because I am using
await Promise.all(myrequests) the first time I see a hangup it throws a socket hang up exception.
This works, but causes some error messages, since my routine for hanging up does some debug logging, and the test is over at this point.
What's the best way to say: "wait for all of these, even when they throw errors?"
My jest/supertest problem block looks something like:
//Send enough concurrent connections to trip the dropout
for(var i = 0;MAX_CONCURRENT_CONNECTIONS+5;i++)
{
requests.push(request(app).get('/'))
}
//wait for all the connections to resolve
const t = async () => {
await Promise.all(requests);
};
//and make sure some drop off
expect(t).toThrow("socket hang up"); //this also doesn't match the error string right but that is because I'm not as experienced in this sort of thing as I'd like!
//however after this, the test ends, and my back end logging causes problems since the test is over!
What's the best way to still wait for all promises in requests even when one throws on await Promise.all(requests)?
I can do the following ugly bit of code, but I'm looking for the right way to write this :)
let gotConnReset = false
try
{
await Promise.all(requests);
}
catch(err)
{
if(err.message == "socket hang up")
{
gotConnReset = true;
}
}
assert(gotConnReset === true);
//wait for all the other requests so that Jest doesn't yell at us!
await Promise.allSettled(requests);
I don't know that Jest has something to help but there's Promise.allSettled which will wait for all promises to fulfill or reject, returning an array of all results. The rejected promises will have a .reason attached.
The main difference is that Jest will be matching error objects rather than using the thrown error matchers so some of the error specific functionality is not there.
test('allSettled rejects', async () => {
class Wat extends Error {}
const resError = new Wat('Nope')
resError.code = 'NO'
const res = await Promise.allSettled([
Promise.resolve(1),
Promise.reject(resError),
Promise.resolve(3),
])
expect(res).toEqual(
expect.arrayContaining([
{ status: 'rejected', reason: new Error('Nope') },
])
)
})
✓ allSettled rejects (2 ms)
If you want to avoid the "loose" matching of the example above which passes with any Error object if the message matches, it might need something like jest-matcher-specific-error or to expect.extend an error matcher
The results can use filter/map or reduce to test rejections directly.
(await Promise.allSettled())
.filter(o => o.status === 'rejected')
.map(o => o.reason)`

Is there any way to break out of a function by running another function in JS

I am working on a simple project and I would like to create a simple helper function that checks for a error in a callback. If there is a error then it should break the whole function that called it. Code example:
//Makes call to database and tries to insert element
db.collection("data").insertOne(
{
key: 'some-data'
}, (error, result) => {
//Return error if something goes wrong - else error is empty
checkError(error, "Unable to load database");
console.log("Succes item added")
}
);
Note: Yes this is node.js but this whole principle could be repeated in js with other callbacks - very simple repeatable error principle.
So in the insertOne function the first argument is some data I am adding to the database. The second argument is the callback function that is called after this async operation is finished. It returns a error which I could just handle by adding this if statement to the callback:
if (error) {
console.error(error);
return;
}
Buuut thats disrespecting the dry principle (bc I write the exact same if statement everywhere with no syntax being changed except the message) and is also distracting when reading the callback function. Now my issue is in the function checkError() even tho I can just print the error with the message or throw the error, I dont actually have a way to break the original callback so that it doesnt cause any more havoc in my database. I will go on to promisify this callback which is a solution. BUT I want to know if there is a way to this in the way I presented it here. Note: I dont want to use the try catch block bc thats replacing a if statement with another two blocks.
My checkError function:
const checkError = function (error, msg = "Something went wrong") {
if (error) console.error(`${msg}: error`);
//Break original block somehow ¯\_(ツ)_/¯
};
If I were to compress my question it would be: how to break a function with another function. Is there any way to achieve this?
I don't think this is possible. But you could achieve something similar with this:
function checkError (error, msg = "Something went wrong") {
if (!error) return false;
console.error(`${msg}: error`);
return true;
};
db.collection("data").insertOne(
{
key: 'some-data'
}, (error, result) => {
//Return error if something goes wrong - else error is empty
if (checkError(error, "Unable to load database")) return;
console.log("Succes item added")
}
);
Things become easier when you use promises.
Often asynchronous APIs provide a promise interface, and this is also the case for mongodb/mongoose, where you can chain a .exec() call to execute the database query and get a promise in return. This gives you access to the power of JavaScript's async/await syntax. So you can then do like this:
async function main() {
// Connect to database
// ...
// Other db transactions
// ...
let result = await db.collection("data").insertOne({ key: 'some-data'}).exec();
console.log("Item added successfully");
// Any other database actions can follow here using the same pattern
// ...
}
main().catch(err => {
console.log(err);
});
The idea here is that await will throw an exception if the promise returned by .exec() eventually rejects. You can either put a standard try...catch construct around it to deal with that error, or you can just let it happen. In the latter case the promise returned by the wrapping async function will reject. So you can deal with the error at a higher level (like done above).
This way of working also removes the need for numerous nested callbacks. Often you can keep the nesting to just one of two levels by using promises.

Why can't I catch error thrown from node-postgres?

I'm having an issue catching an error thrown from the Node-Postgres NPM package.
The issue seems simple on the surface, but I've tried everything I can think of.
My code is like the following:
import { Pool } from 'pg' // Import postgres connection pool
const pgPool = new Pool()
async function queryDatabase() {
try {
// Force TypeError by passing undefined
let queryResult = await pgPool.query( undefined )
if ( queryResult.rows.length > 0 ) {
return queryResult.rows[0]
}
return false
} catch( err ) {
// Never Reached
return new Error( 'Test error' )
}
}
queryDatabase()
And the error is as follows:
TypeError: Client was passed a null or undefined query
at Client.query (~/.../node_modules/pg/lib/client.js:479:11)
The error itself is pretty self-explanatory. I'm forcing the error here, for the sake of trying to handle it in the event that undefined gets passed by mistake. I realize that I can simply perform a check to make sure the input is never null or undefined, but that's not my main concern.
My worry is if I can't catch this error thrown from this package, how many other unforeseen cases am I going to encounter where I simply can't catch and handle a thrown error.
I've tried numerous different approaches - The Async/Await Try/Catch method, shown above - I've tried pgPool.query().then().catch() - Combinations of the two. I've even tried running the catch against the Pool instance itself. No matter what I do, I can't handle the exception without using Node's process.on('unhandledRejection', ...), which is of course a bad idea.
I've been racking my brain on this for hours. Is there any way that I can catch and handle errors like this, so it's not crashing my server every time? Thanks in advance!
I was able to reproduce this and it seems to be an actual bug in the pg-library.
According to the source if you call .query on a pool instance, this instance will attempt to connect and get a client. In this connect-callback the actual query is dispatched to the client-module, which will throw the mentioned type error if the query is nil.
This error is thrown synchronously (i.e. the error is not passed to the callback argument, e.g. callback(new TypeError("...")) and since there's no try/catch around the client.query call in the pool's connect-callback, the error will not be caught by your try/catch.
A potential fix would be to wrap the client.query call in a try catch:
client.once('error', onError)
this.log('dispatching query')
try {
client.query(text, values, (err, res) => {
this.log('query dispatched')
client.removeListener('error', onError)
if (clientReleased) {
return
}
clientReleased = true
client.release(err)
if (err) {
return cb(err)
} else {
return cb(undefined, res)
}
})
}catch(err) {
return cb(err)
}
So for now, you probably should create an issue on github and wait for the bugfix or fork the repo and use above workaround, I'm afraid.

In Jest, how can I make a test fail?

I know I could throw an error from inside the test, but I wonder if there is something like the global fail() method provided by Jasmine?
Jest actually uses Jasmine, so you can use fail just like before.
Sample call:
fail('it should not reach here');
Here's the definition from the TypeScript declaration file for Jest:
declare function fail(error?: any): never;
If you know a particular call should fail you can use expect.
expect(() => functionExpectedToThrow(param1)).toThrow();
// or to test a specific error use
expect(() => functionExpectedToThrow(param1)).toThrowError();
See Jest docs for details on passing in a string, regex, or an Error object to test the expected error in the toThrowError method.
For an async call use .rejects
// returning the call
return expect(asyncFunctionExpectedToThrow(param1))
.rejects();
// or to specify the error message
// .rejects.toEqual('error message');
With async/await you need to mark the test function with async
it('should fail when calling functionX', async () => {
await expect(asyncFunctionExpectedToThrow(param1))
.rejects();
// or to specify the error message
// .rejects.toEqual('error message');
}
See documentation on .rejects and in the tutorial.
Also please note that the Jasmine fail function may be removed in a future version of Jest, see Yohan Dahmani's comment. You may start using the expect method above or do a find and replace fail with throw new Error('it should not reach here'); as mentioned in other answers. If you prefer the conciseness and readability of fail you could always create your own function if the Jasmine one gets removed from Jest.
function fail(message) {
throw new Error(message);
}
You can do it by throwing an error. For example:
test('Obi-Wan Kenobi', () => {
throw new Error('I have failed you, Anakin')
})
Copy/pasta failing test:
it('This test will fail', done => {
done.fail(new Error('This is the error'))
})
Here are certain scenarios where some of the answers won't work. In a world of async-await, it is quite common to have try-catch logic like so.
try {
await someOperation();
} catch (error) {
expect(error.message).toBe('something');
}
Now imagine if someOperation() somehow passed, but you were expecting it to fail, then this test will still pass because it never went to the catch block. So what we want is to make sure that the test fails if someOperation does not throw an error.
So now let's see which solutions will work and which won't.
Accepted answer won't work here because the throw will be catched again.
try {
await someOperation();
throw new Error('I have failed you, Anakin');
} catch (error) {
console.log('It came here, and so will pass!');
}
The answer with true === false also won't work because, assertions too throw an error like above which will be catched.
try {
await someOperation();
expect(true).toBe(false); // This throws an error which will be catched.
} catch (error) {
console.log('It came here, and so will pass!');
}
The one solution that DOES WORK (as shown in #WhatWouldBeCool's answer) for this case is below. Now it explicitly fails the test.
try {
await someOperation();
fail('It should not have come here!')
} catch (error) {
console.log('It never came here!');
}
Update May-2022
The fail() function is not officially supported by Jest anymore. Instead, you can do a couple of things to fail explicitly.
Method-1
You can wrap your promise function within expect and tell jest the function should reject with the given error. If the someOperation() somehow passes, jest will throw an error. If the someOperation() fails for any other reason other than the one you specified, it will throw an error. There are also different methods other than toThrowError() that you can use.
await expect(someOperation()).rejects.toThrowError('error!')
Method-2
You can declare explicitly how many assertions you expect in your test. If that doesn't match because someOperation() never failed, jest would throw an error.
expect.assertions(1)
try {
await someOperation();
} catch (error) {
expect(error.message).toBe('something');
}
Dont think there is, discussed here: https://github.com/facebook/jest/issues/2129
A lot of good ideas here. Only to add extra info about testing async code which may lead to trying to make Jest explicitly fail, check the docs for Testing Asynchronous Code https://jestjs.io/docs/en/asynchronous
To test a function that returns a Promise that resolves, it's important to return the Promise, so Jest knows that the test is done only when the Promise is resolved or it'll time out:
test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter')
})
})
To test a function that returns a Promise that rejects, it's important to return the Promise, so Jest knows that the test is done only when the Promise is rejected or it'll time out. And also have to say how many assertions Jest needs to count or it won't fail if the Promise is resolved - which is wrong in this case -:
test('the fetch fails with an error', () => {
expect.assertions(1)
return fetchData().catch(e => expect(e).toMatch('some specific error'))
})
You can always do something like this :)
expect(true).toBe(false);
The done callback passed to every test will throw an error if you pass a string to it.
for instance
it('should error if the promise fails', async (done) => {
try {
const result = await randomFunction();
expect(result).toBe(true);
done();
} catch (e) {
done('it should not be able to get here');
}
});
In this following code if the randomFunction throws an error it will be caught in the catch and with auto fail due to the string being passed to done.
Add jest-fail-on-console npm package, then on your jest.config.js
import failOnConsole from 'jest-fail-on-console'
failOnConsole();
This will fail a test once there is a console error or warning done by jest because of an error or warning thrown in the test item.
I just ran into this one, and after some digging, I found the root of the issue.
Jest, since its inception, has been compatible with Jasmine. Jasmine provided a fail function for programmatically fail the test. This is very useful for cases where throwing an error would cause the test to pass incorrectly (overly-simplified example, but hopefully illustrates the use-case):
function alwaysThrows() {
throw new Error();
}
describe('alwaysThrows', () => {
it('should throw', () => {
try {
alwaysThrows();
// here if there is nothing to force a failure, your
// test could "pass" as there are no failed expectations
// even though no error was thrown. If you just put the
// following to prevent that, you actually force the test
// to always pass:
throw new Error('it should have failed');
// that's why instead you use Jasmine's `fail(reason)` function:
fail('it should have failed');
} catch(err) {
expect(err).toBeDefined();
}
});
)
});
So, what has happened is this:
originally Jest did have a fail() function defined, because its default test runner was jest-jasmine2, which provided fail().
In Jest version 27 (or thereabouts), Jest replaced jest-jasmine2 with jest-circus as the default test runner. jest-circus does not implement a fail() function. This was reported as a bug on July 28th 2021: https://github.com/facebook/jest/issues/11698
Jest's type definitions (maintained in DefinitelyTyped) did not remove the fail() function, so autocompletion and the TypeScript compiler still think that it exists and can be used. There is an issue going on in DefinitelyTyped as well: https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/55803
The issue with this thread is that they have decided not to remove it from the type definitions as it is marked as a "regression" in the Jest repository. Unfortunately, the Jest repository's thread has no official response about whether or not they will support this in the future, so the type definitions are in limbo.
So, long story short, Jest doesn't support fail() by default, but knowing that it's a matter of the default task runner, you can restore the fail() functionality by telling Jest to use the jest-jasmine2 runner instead of the default jest-circus runner:
npm i -D jest-jasmine2
configure the Jest config:
module.exports = {
testRunner: "jest-jasmine2"
};
P.S.: usually there is a better way than try/catch to account for errors in your actual test cases. You can see an example of different ways to handle errors without requiring try/catch in both synchronous and asynchronous contexts here: https://gist.github.com/joeskeen/d9c053b947e5e7462e8d978286311e83
You can throw an error simulating an error thrown by the application and then expect its message to be different from what it actually is.
try {
await somthingYouExpectToFail();
throw new Error("Fail!");
} catch (error) {
expect(error.message).not.toBe("Fail!");
}

How do I get a meaningful test error when assertion is in a promise?

I'm having a hard time getting meaningful failures in tests when I need to check things in a promise.
That's because most testing frameworks use throw when an assertion fails, but those are absorbed by the then of promises...
For example, in the following I'd like Mocha to tell me that 'hello' isn't equal to 'world'...
Promise.resolve(42).then(function() {
"hello".should.equal("world")
})
Complete fiddle here
With Mocha we can officially return the promise, but this swallows completely the error and is thus much worse...
Note: I'm using mocha and expect.js (as I want to be compatible with IE8)
With Mocha we can officially return the promise, but this swallows completely the error and is thus much worse...
In your fiddle, you are using Mocha 1.9 which dates from April 2013, and did not support returning promises from tests. If I upgrade your fiddle to the latest Mocha, it works just fine.
This is less of an answer rather than a suggestion? Using the before hook would be useful here.
describe('my promise', () => {
let result;
let err;
before(done => {
someAsync()
.then(res => result = res)
.then(done)
.catch(e => {
err = e;
done();
});
});
it('should reject error', () => {
err.should.not.be.undefined(); // I use chai so I'm not familiar with should-esque api
assert.includes(err.stack, 'this particular method should throw')
});
});
You could also use sinon to make synchronous mocks and then use whatever should.throw functionality your assertion library provides.
To test a failing Promise, do this:
it('gives unusable error message - async', function(done){
// Set up something that will lead to a rejected promise.
var test = Promise.reject(new Error('Should error'));
test
.then(function () {
done('Expected promise to reject');
})
.catch(function (err) {
assert.equal(err.message, 'Should error', 'should be the error I expect');
done();
})
// Just in case we missed something.
.catch(done);
});

Categories

Resources