timeout when testing promise rejection with sinon and chai-as-promise - javascript

I have a function which wraps a third party child-process-promise, which itself wraps spawn in promise.
let spawn = require('child-process-promise').spawn;
run(cmd, args = []) {
return new Promise(async (resolve, reject) => {
let command = spawn(cmd, args);
let childProcess = command.childProcess;
let result = '';
childProcess.stdout.on('data', (data) => {
result += data.toString();
});
try {
const res = await command;
resolve(result);
} catch (err) {
if (err.code && err.code === 'ENOENT') {
reject(`Command "${cmd}" not found`);
} else {
reject('Exec err' + err);
}
}
});
}
Testing the resolve was quite straightforward and I manage to get my stdout data passed to result then detected by chai-as-promised using await expect(shellRun).to.eventually.become('hello world');
Our problem is when we try to test the catch part of our method.
const ERROR = 'someError';
beforeEach(() => {
sandbox = sinon.createSandbox();
spawnEvent = new events.EventEmitter();
spawnEvent.stdout = new events.EventEmitter();
spawnStub = sandbox.stub();
spawnStub.returns({ childProcess: spawnEvent });
spawnStub.withArgs(ERRORED, ARGUMENTS).throws(ERROR));
shell = proxyquireStrict('../../lib/utils/spawnWrapper', {
'child-process-promise': {
spawn: spawnStub
}
}
);
});
afterEach(() => {
sandbox.restore();
});
describe('when a generic error occurs', () => {
it('should reject the promise', async () => {
const shellRun = run(ERRORED, ARGUMENTS);
await expect(shellRun).to.eventually.be.rejectedWith('Exec err' + ERROR);
});
});
We manage to get childProcessPromiseSpawn to throw an error conditionally by playing with ou spawnStub.withArgs. But a timeout is encountered:
(node:15425) UnhandledPromiseRejectionWarning: Error: someError
(node:15425) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 838)
1 failing
1) run method
when a generic error occurs
should reject the promise:
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
We tried spawnStub.withArgs(ERRORED, ARGUMENTS).rejects instead of throws without more success. Changing someError for new Error('someError') doesn't work either.
We also tried to catch at test level
try {
await run(ERRORED, ARGUMENTS);
} catch (e) {
expect(e).to.equal('Exec err' + ERROR);
}
But the timeout still occurs.

This depend of which testing library you are using. Each library has a dedicated timeout.
For mocha you can define it in the test suite or for a unique test
https://mochajs.org/#timeouts

Related

nodejs promises giving Unhandled Promise Rejection which seems to be handled properly

The problem is that i am getting UNhandledPromiseRejection error eveen though i think i have handled all the cases. The code flows from profileRoutes to Controller to Utils where the error comes first.
Inside the profileRoutes.js
router.get('/:username', async (r, s) => {
try{
let profileData = await getProfileData(r.params.username);
s.json({ success: true, payload: profileData });
}catch(err){
console.log('ending request processing by responding a error');
s.status(500).json({ success: false, message: 'err[0].message' });
}
});
Inside the controllers/index.js
const fetchQueue = [getUserRepos];
async function getProfileData(username) {
let profileData = {};
try{
let results = await Promise.all(fetchQueue.map(item => item(username)));
for (let i = 0; i < results.length; i++) {
profileData[getKeys[i]] = results[i];
}
return profileData;
}catch(err){
console.log('error log in controller/index getProfileData function');
throw err;
}
}
const getUserRepos = async (username) => {
try {
// const res = await utils.gqlSender(username, 'userRepos', { createdAt });
const res = await utils.gqlSender(username, 'userReposData');
return res.user.repositories;
} catch (err) {
console.log('error log in controller/index getUserRepos function');
throw err;
}
};
Inside the utils/index.js
const gqlSender = async (username, type, opt = {}) => {
axios.post('', {
query: gqlQuery(username, type, opt) // generates a needed graphQL query
}).then(res => {
if(res.data.errors) { // this is where the error is recieved and so i reject promise.
console.log('bef###re');
return Promise.reject (res.data.errors);
}
console.log('###',res.data);
return res.data;
}).catch(err => {
console.log('error in making axios request inside utils/index gqlSender function');
throw err;
// return Promise.reject(err);
});
The stack trace on making get request to /:username is-
error log in controller/index getUserRepos function
error log in controller/index getProfileData function
ending request processing by responding a error
bef###re
error in making axios request inside utils/index gqlSender function
(node:11260) UnhandledPromiseRejectionWarning: [object Array]
(node:11260) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
(node:11260) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
I dont think i am missing any Promise Rejection.
Any help is appreciated. Thanks.
i have referred these answers previously -
What's the difference between returning value or Promise.resolve from then()
Do I need to return after early resolve/reject?
Your gqlSender function is not returning the promise that will get rejected, so it is not handled anywhere. You should write either
const gqlSender = (username, type, opt = {}) => {
return axios.post('', {
// ^^^^^^
query: gqlQuery(username, type, opt) // generates a needed graphQL query
}).then(res => {
if (res.data.errors) {
console.log('error in making axios request inside utils/index gqlSender function');
throw res.data.errors;
} else {
console.log('###',res.data);
return res.data;
}
});
};
or
const gqlSender = async (username, type, opt = {}) => {
// ^^^^^
const res = await axios.post('', {
query: gqlQuery(username, type, opt) // generates a needed graphQL query
});
if (res.data.errors) {
console.log('error in making axios request inside utils/index gqlSender function');
throw res.data.errors;
} else {
console.log('###',res.data);
return res.data;
}
}

How to test status of a promise inside Promise.finally() without awaiting it in production code

I am using Promise.prototype.finally() (or try-catch-finally in an async function) in my production code to execute some follow-up code without changing resolution/rejection status of the current promise.
However, in my Jest tests, I would like to detect that the Promise inside finally block wasn't rejected.
edit: But I don't want to actually await the Promise in my "production" code (there I care only about errors re-thrown from catch, but not about errors from finally).
How can I test for that? Or at least how to mock the Promise.prototype to reject the current promise on exceptions from finally?
E.g. if I would be testing redux action creators, the tests pass even though there is a message about an unhandled Promise rejection:
https://codesandbox.io/s/reverent-dijkstra-nbcno?file=/src/index.test.js
test("finally", async () => {
const actions = await dispatchMock(add("forgottenParent", { a: 1 }));
const newState = actions.reduce(reducer, undefined);
expect(newState).toEqual({});
});
const dispatchMock = async thunk => {...};
// ----- simplified "production" code -----
const reducer = (state = {}, action) => state;
const add = parentId => async dispatch => {
dispatch("add start");
try {
await someFetch("someData");
dispatch("add success");
} catch (e) {
dispatch("add failed");
throw e;
} finally {
dispatch(get(parentId)); // tests pass if the promise here is rejected
}
};
const get = id => async dispatch => {
dispatch("get start");
try {
await someFetch(id);
dispatch("get success");
} catch (e) {
dispatch("get failed");
throw e;
}
};
const someFetch = async id => {
if (id === "forgottenParent") {
throw new Error("imagine I forgot to mock this request");
}
Promise.resolve(id);
};
dispatch(get(parentId)); // tests pass if an exception is thrown here
There is no exception throw in that line. get(parentId) might return a rejected promise (or a pending promise that will get rejected later), but that's not an exception and won't affect control flow.
You might be looking for
const add = parentId => async dispatch => {
dispatch("add start");
try {
await someFetch("someData");
dispatch("add success");
} catch (e) {
dispatch("add failed");
throw e;
} finally {
await dispatch(get(parentId));
// ^^^^^
}
};
Notice that throwing exceptions from a finally block is not exactly a best practice though.
edit: more general solutions available on https://stackoverflow.com/a/58634792/1176601
It is possible to store the Promise in a variable accessible in some helper function that is used only for the tests, e.g.:
export const _getPromiseFromFinallyInTests = () => _promiseFromFinally
let _promiseFromFinally
const add = parentId => async dispatch => {
...
} finally {
// not awaited here because I don't want to change the current Promise
_promiseFromFinally = dispatch(get(parentId));
}
};
and update the test to await the test-only Promise:
test("finally", async () => {
...
// but I want to fail the test if the Promise from finally is rejected
await _getPromiseFromFinallyInTests()
});

I cant handle promise rejections

I have a service that analyses websites, compresses their sources like CSS Documents, Images etc. I have 2 functions, one is Socket.IO socket.on() method with async callback function. Another is main function for service.
socket.on('run', async options => {
debug(`${options.target} Adresine Bir Kullanıcı İstek Yaptı!`);
let user = null;
console.log(options);
if(options.token) {
user = await User.findById(jwt.verify(options.token, config.get('jwtPrivateKey'))._id);
options.userId = user._id.toString();
} else if(options.visitor) {
user = await Visitor.findById(options.visitor._id);
if(user.report) {
return socket.emit('error', new Error('You have exceeded your report limit'));
} else {
options.userId = user._id.toString();
}
}
if(options.userId) {
let userType = await UserType.find({ name: user.type });
if(userType.length > 0 && ((user.type == 'Visitor' && user.report == undefined) || (user.reports.length < userType[0].rights.reportsLimit.limit || userType[0].rights.reportsLimit.unlimited))) {
options.rights = userType[0].rights;
let { error, data } = await wrapper(runService(options.target, options, socket));
if(error) {
console.log('Here', error);
return socket.emit('error', error);
}
.
.
.
}
.
.
.
}
});
In the above function,
let { error, data } = await wrapper(runService(options.target, options, socket));
if(error) {
console.log('Here', error);
return socket.emit('error', error);
}
This part is important, because I call my main async service function runService with my async function wrapper function that is named wrapper. The wrapper function is this;
const wrapper = promise => (
promise
.then(data => ({ data, error: null }))
.catch(error => ({ error, data: null }))
);
In my main async service function, I only throw an error;
async function runService(target, options, socket) {
throw new Error('any error');
}
But the expected output is much different from actual output. Here is the output of this code;
Here Error: any error
at startService (C:\Projeler\OpDetect\Background-Service\lib\app.js:404:11)
at Socket.socket.on (C:\Projeler\OpDetect\Background-Service\app.js:73:57)
at process._tickCallback (internal/process/next_tick.js:68:7)
(node:16600) UnhandledPromiseRejectionWarning: Error: any error
at startService (C:\Projeler\OpDetect\Background-Service\lib\app.js:404:11)
at Socket.socket.on (C:\Projeler\OpDetect\Background-Service\app.js:73:57)
at process._tickCallback (internal/process/next_tick.js:68:7)
(node:16600) UnhandledPromiseRejectionWarning: Unhandled promise rejection.
This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
(node:16600) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate
the Node.js process with a non-zero exit code.
My expectation about the output is, like this;
Here Error: any error
at startService (C:\Projeler\OpDetect\Background-Service\lib\app.js:404:11)
at Socket.socket.on (C:\Projeler\OpDetect\Background-Service\app.js:73:57)
at process._tickCallback (internal/process/next_tick.js:68:7)
Because I already handled the promise rejection with my wrapper function and catched the rejection, Why is 2 more UnhandledPromiseRejectionWarning errors on rejection?
Also, the line,
return socket.emit('error', error);
is not calling for no reason. It should have been called when the if statement truthy. Why is not this socket.emit function called?
As best practice use try {} catch(){} with async/await.
For ex.
userUtils.signUp = async (userName) => {
try {
const callFunction = await userUtils.checkExistancy(userName);
if (!callFunction.isExist) {
...
} else {
...
}
} catch (err) {
console.log(err);
throw err;
}
};
in your case it will be like
socket.on('run', async options => {
try {
user = await User.findById(jwt.verify(options.token, config.get('jwtPrivateKey'))._id);
options.userId = user._id.toString();
return true;
} catch (err) {
throw err;
}});

Executing an async function in a loop

I'm writing a function, that loops through port numbers until it finds an open one:
async function findPort (port, app, logger) {
const lookupPort = () => {
return portOpen = new Promise( (resolve, reject) => {
app.listen(port, () => {
logger.log("info", `Server listening on port: ${port}`);
resolve(true);
})
.on('error', (err) => {
logger.log("warn", "port closed: " + err.port);
reject(false);
});
});
}
let portOpen = false;
while (portOpen === false){
portOpen = await lookupPort();
port++;
}
When executed, it iterates through one port and then trows an exception:
(node:2869) UnhandledPromiseRejectionWarning: Unhandled promise rejection.
There are 2 issues with the code above:
1) while loop stops its execution because lookupPort function throws an error (promise is rejected on reject(false); line) which is not caught (UnhandledPromiseRejectionWarning: Unhandled promise rejection.). To fix this try to wrap the code inside while loop into try/catch construction:
while (portOpen === false){
try {
portOpen = await lookupPort();
}
catch (error) {
// handle an error here
}
port++;
}
Or if you do not want to handle the promise you reject inside lookupPort just remove reject(false); line.
2) lookupPort function assigns a new Promise() to portOpen variable. So even if error will be handled by try/catch, portOpen === false condition will evaluate to false because portOpen will be equal to a Promise object. If there is no reason to assin portOpen the value of the new Promise you should remove it.

UnhandledPromiseRejectionWarning on async await promise

UnhandledPromiseRejectionWarning on async await promise
I have this code:
function foo() {
return new Promise((resolve, reject) => {
db.foo.findOne({}, (err, docs) => {
if (err || !docs) return reject();
return resolve();
});
});
}
async function foobar() {
await foo() ? console.log("Have foo") : console.log("Not have foo");
}
foobar();
Which results with:
(node:14843) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): false
(node:14843) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Note: I know I can solve this issue like this:
foo().then(() => {}).catch(() => {});
But then we are "back" to callbacks async style.
How do we solve this issue?
Wrap your code in try-catch block.
async function foobar() {
try {
await foo() ? console.log("Have foo") : console.log("Not have foo");
}
catch(e) {
console.log('Catch an error: ', e)
}
}
then(() => {}).catch(() => {}) isn't needed because catch doesn't necessarily should go after then.
UnhandledPromiseRejectionWarning means that a promise weren't synchronously chained with catch, this resulted in unhandled rejection.
In async..await, errors should be caught with try..catch:
async function foobar() {
try {
await foo() ? console.log("Have foo") : console.log("Not have foo");
} catch (error) {
console.error(error);
}
}
The alternative is to handle errors at top level. If foobar is application entry point and isn't supposed to be chained anywhere else, it's:
foobar().catch(console.error);
The problem with foo is that it doesn't provide meaningful errors. It preferably should be:
if (err || !docs) return reject(err);
Also, most popular callback-based libraries have promise counterparts to avoid new Promise. It mongoist for mongojs.
Every solution here just silences the error, but you should probably handle the error instead.
How you handle it depends on the error and on what part of the application you're in. Here are some examples.
You're writing an app
If you're writing a node app and something throws, you might want to use process.exit(1) to quit the app and display the error to the user:
async function init() {
await doSomethingSerious();
}
init().catch(error => {
console.error(error);
process.exit(1)
});
You're writing a module
If the code expects an error, you can catch it and use it as a value instead:
module.exports = async function doesPageExist(page) {
try {
await fetchPage(page);
return true;
} catch (error) {
if (error.message === '404') {
return false;
}
// Unrecognized error, throw it again
throw error;
}
}
Notice that this example re-throws the error when it's not the expected one. This is fine. It's the final user’s responsibility to handle network errors:
const doesPageExist = require('my-wonderful-page-checker');
async function init() {
if (await doesPageExist('https://example.com/nope')) {
console.log('All good')
} else {
console.log('Page is missing 💔')
}
}
// Just like before
init().catch(error => {
console.error(error);
process.exit(1)
});
You're the user
If you're seeing this error when using a prepackaged application via command line, like webpack or babel, it might mean that the application had an error but it was not handled. This depends on the application or your input is not correct. Refer to the application’s manual.

Categories

Resources