Jest mockRejectedValue throws unhandled promise rejection in node - javascript

I'm trying to write a test in jest but keep getting UnhandledPromiseRejectionWarning when I try to use mockRejectedValue
The code looks like this:
it('Should set error message when call fails', async () => {
const context = mockActionsContext();
const user = {
username: 'alice',
password: 'password'
};
const getError = new Error('network error');
(AuthService.login as jest.Mock) = jest.fn().mockRejectedValue(getError);
await actions[ActionTypes.USER_LOGIN](context, user);
// Check is the commits are called
expect((context.commit as any).mock.calls).toEqual([
[MutationTypes.USER_LOGIN],
[MutationTypes.USER_LOGIN_ERROR, 'Oops, something went wrong. Try again later!']
]);
// Login service is called with user login
expect(AuthService.login as jest.Mock).toHaveBeenCalledWith(user);
});
The AuthService.login returns an axios.post which I try to overwrite with a mock.
actions[ActionTypes.USER_LOGIN](context, user) calls the Authservice.login
The test is passing but I don't want any unhandled promise rejection. Anybody an idea how to fix it?
Edit
#goodmorningasif thanks for your reply.
I've been looking at it too long I thing :)
The action looks as following:
[ActionTypes.USER_LOGIN]: ({ commit }: Context, payload: User) => {
return new Promise((resolve, reject) => {
commit(MutationTypes.USER_LOGIN);
AuthService.login(payload)
.then((token) => {
commit(MutationTypes.USER_LOGIN_SUCCESS, token);
localStorage.setItem('user-token', token);
client.defaults.headers.common.Authorization = `Bearer ${token}`;
resolve(token);
})
.catch((error) => {
let errorMessage = 'Oops, something went wrong. Try again later!';
if (error?.response?.status === 401) {
errorMessage = 'Unknown username and password combination!';
}
localStorage.removeItem('user-token');
commit(MutationTypes.USER_LOGIN_ERROR, errorMessage);
reject(error);
});
});
},
SOLUTION
In my case the action is returning a promise witch would get rejected. In the test, I'm calling the action directly and not catching the rejection.
await actions[ActionTypes.USER_LOGIN](context, user).catch(() => null);
This fixed it.

Can we see the actions and reducer code? It's possible that there's an error in your error :)
You're testing that the login function is called and the action returns the error message you set but you're making an assumption about what causes the error. Maybe it's not because of the mockRejectedValue/'network error'.
I'd suggest including the actual error message in the action payload as well as your error message: one is for developers and debugging and one is for the user to know what to do next.
I also found this helpful on understanding UnhandledPromiseRejectionWarning: https://thecodebarbarian.com/unhandled-promise-rejections-in-node.js.html
Good instinct to figure out the issue and not be content with the test passing, by the way!

Related

Discord.js v13 How to prevent the bot from crashing on attempted message deletion?

I have this 'clear' command for my discord bot and it should work like this:
-user types in !mute
the bot deletes this many messages (up to 100)
the bot sends a message saying that these messages were deleted
the bot deletes that message 5 seconds later
It all works except for the last part, you see if after executing part 2 the message is deleted by another source then the bot can't find a message to delete and crashes.
The code is as follow:
module.exports = {
name: 'clear',
description: "clears messages",
async execute(message, args)
{
if(!args[0]) return message.reply("Please specify how many messages you want to clear!");
if(isNaN(args[0])) return message.reply("Please enter a number of messages you want to clear!");
if(args[0] > 100) return message.reply("You can't delete more than 100 messages!");
if(args[0] < 1) return message.reply("You can't delete less than 1 message!");
await message.channel.messages.fetch({limit: args[0]}).then(messages =>{
message.channel.bulkDelete(messages).then(() => {
message.channel.send("Deleted " + args[0] + " messages") .then(msg => {
let id = msg.id;
setTimeout(function(){
if(message.channel.messages.fetch(id))
{
try {
msg.delete()
}
catch (error) {
console.log(error)
return
}
}
}, 5000);
})
});
})
}
}
The error I'm getting is:
C:\Users\Miki\Desktop\discord boty\jajco bot\node_modules\discord.js\src\rest\RequestHandler.js:350
throw new DiscordAPIError(data, res.status, request);
^
DiscordAPIError: Unknown Message
at RequestHandler.execute (C:\Users\Miki\Desktop\discord boty\jajco bot\node_modules\discord.js\src\rest\RequestHandler.js:350:13)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async RequestHandler.push (C:\Users\Miki\Desktop\discord boty\jajco bot\node_modules\discord.js\src\rest\RequestHandler.js:51:14)
at async MessageManager._fetchId (C:\Users\Miki\Desktop\discord boty\jajco bot\node_modules\discord.js\src\managers\MessageManager.js:219:18) {
method: 'get',
path: '/channels/943105549795471443/messages/946096412922368100',
code: 10008,
httpStatus: 404,
requestData: { json: undefined, files: [] }
}
As you can see I've tried using the try...catch() but it didn't fix the problem. Any ideas on what to do? Or maybe there is a mistake in my code like a missing import or some other thing like that?
First off lets take a look at this code:
try {
msg.delete();
} catch (error) {
return console.log(error);
}
This code won't do what you expect. msg.delete(); is a function that returns a Promise. A try/catch structure won't do anything if the promise fails. So instead, to catch the error, you have to use the catch method on the Promise, just as you used .then() earlier, you'll have to use .catch().
So your new code will be like this:
msg.delete().catch(error => console.error(error);
// Or, even better:
msg.delete().catch(console.error);
That is the exact same thing here:
if (message.channel.messages.fetch(id))
That will always be true because the fetch() function will return a Promise ans since it is neither a 0, '' (empty string),NaN, false, undefined or null See here for false values
Instead, what you're trying to do is check if the messages contain your id:
message.channel.messages.fetch(id).then(msg => {
// Do what you want with the fetched msg
}).catch(error => {
// Console error why it failed to fetch the id (probably because it doesn't exist or got deleted)
message.reply('Failed to fetch the id');
});
Lastly this code also has some issues:
message.channel.bulkDelete(messages).then(() => {
messages is a Collection, not a number, bulkDelete() waits for a number so instead do this:
message.channel.bulkDelete(messages.size).then(() => {
I personnally don't see why you have to fetch the message, you can simply do this:
message.channel.messages.fetch({
limit: args[0]
}).then(messages => {
message.channel.bulkDelete(messages.size).then(() => {
message.channel.send(`Deleted ${args[0]} messages`).then(msg => {
setTimeout(() => msg.delete(), 5000);
});
});
});
All you need to do is to await msg.delete() inside your try block. A promise must be settled before it can be tested for any error.
try {
// Add 'await'
await msg.delete();
} catch (error) {
return console.log(error);
}
If you want to void the error to have the function fail silently:
msg.delete()
.catch(() => null);
You can also check Message#deletable before having your client attempt to delete it:
if (msg.deletable) {
// I'd still recommend adding your preferred error handling
msg.delete();
}
return message.reply({ content: "Done" })
.then(m => setTimeout(() => message.delete().catch(() => null), 2500))
use it like this it'll solve the issue for d.js v13.6

How to avoid ` PromiseRejectionHandledWarning: Promise rejection was handled asynchronously`?

I'm getting PromiseRejectionHandledWarning: Promise rejection was handled asynchronously warning for my code, it also fails Jest tests. I've read that's Promise rejection should be handled at the same place they're defined and seem to understand the logic and the reasons. But it looks like something is wrong with my understanding as what I expect to be synchronous handling still cause warning.
My typescript code is below.
Promise, that is rejected is sendNotification(subscription, JSON.stringify(message)). From my understanding it's handled right away using .catch call, but probably I'm missing something. Can anyone, please, point me to my mistake?
private notify(tokens: string[], message: IIterableObject): Promise<any> {
const promises = [];
tokens.forEach(token => {
const subscription = JSON.parse(token);
this.logger.log('Sending notification to subscription', {subscription, message})
const result = this.WebPushClient
.sendNotification(subscription, JSON.stringify(message))
.catch(e => {
this.logger.log('Send notification failed, revoking token', {
subscription,
message,
token,
e
})
return this.revokeToken(token).catch(error => {
this.logger.error('Failed to revoke token', {
token,
error,
})
return Promise.resolve();
});
});
promises.push(result);
});
return Promise.all(promises);
}
I found the issue.
In Jest you can't just mock return value with rejected promise. You need to wrap it in special workaround function:
const safeReject = p => {
p.catch(ignore=>ignore);
return p;
};
And then wrap the Promise before return it
const sendNotification = jest.fn();
sendNotification.mockReturnValue(safeReject(Promise.reject(Error('test error'))));
You can also use mockImplementation (I'm using jest 28).
jest.fn().mockImplementation(() => Promise.reject(new Error('test')))

Axios doesn't give neither .then or .catch response after POST

I'm trying to do a post in a localhost SQL DB with react/nodejs. Fortunately it does POST and the information goes to the DB. I wanted to put an alert() inside the .then() if it was sucessfull and another alert() inside the catch() if there was a error. The problem is that it gives both 'success' alert and 'error' alert when posting, although it posts all good in the database. Am I missing something?
const submitForm = () => {
Axios.post('http://localhost:3001/api/insert',{
manager:manager,
decisionDate:decisionDate.toLocaleDateString(),
}).then(alert('Success'))
.catch(alert('Failure'))
}
Your syntax is incorrect for the then callback, which results in an error that is then caught by catch. Update the callback to be the following:
const submitForm = () => {
Axios.post('http://localhost:3001/api/insert',{
manager:manager,
decisionDate:decisionDate.toLocaleDateString(),
}).then(() => alert('Success'))
.catch(() => alert('Failure'))
}
Your syntax is not right which results in an error that is then caught by catch clause.
Update the callback to be the following:
axios.post('http://localhost:3001/api/insert', {
manager:manager,
decisionDate:decisionDate.toLocaleDateString(),
}).then(function (response) {
console.log('Success', response);
}).catch(function (error) {
console.log(error);
});
Please refer to axios documentation

Am I using .then() in the correct way?

Like the title says, it's clear based on running authHandler (see below) that it's not doing what I want it to. But I don't know why.
Basically, what I was wanting authHandle to do is first register a new user, generate a userUID and token, and subsequently update the state. Then, once that has been achieved, run the second half of the code to store the new user's user name.
Right now. All it does is registering the user (so initiating the first half of the code) but not executing the second .then() half.
I suspect it's something to do with the states between the first half and second half of authHandler.
I'm hoping someone can help me figure out where I went wrong ><
authHandler = () => {
return new Promise ((resolve, reject) => {
const authData = {
email: this.state.controls.email.value,
password: this.state.controls.password.value
};
this.props.onTryAuth(authData, this.state.authMode); // registers a new user and gives a userUID and token
})
.then(() => {
this.props.onAddUserData(
this.state.controls.userName.value,
)
}) //store the username of the new user
.catch(err => {
console.log(err);
alert("Oops! Something went wrong, please try again")
})
};
You need to either resolve or reject the promise you're creating - right now it's stuck in a pending state so whatever gets called in the then of authHandler() will never happen. You should also be returning the promise, calling then and catch where you do won't work properly. this code snippet reorganizes it in a way that should work.
authHandler = () => {
return new Promise ((resolve, reject) => {
try {
const authData = {
email: this.state.controls.email.value,
password: this.state.controls.password.value
};
this.props.onTryAuth(authData, this.state.authMode); // registers a new user and gives a userUID and token
this.props.onAddUserData(this.state.controls.userName.value)
resolve('done')
} //store the username of the new user
catch(err){
console.log(err);
alert("Oops! Something went wrong, please try again")
reject(err)
}
})
};

Promise hell, Anti-pattern and Error Handling

I am a newbie in node.js environment. I read a lot of source about implementing Promises and chaining them together. I am trying to avoid anti-pattern implementation but I could not figure it out how can I do it.
There is a User Registration flow in the system.
First, I check the username.
if there is no user in the DB with this username I create a User model and save it to the DB.
Can you kindly see my comment inline ?
app.js
RegisterUser("existUser","123").then(user=>{
//send response, etc
}).catch(er => {
console.log(er);
//log error,send proper response, etc
// Should I catch error here or inner scope where RegisterUser implemented ?
});
userService.js
function RegisterUser(username, password) {
return new Promise((resolve, reject) => {
GetUser(username)
.then(user=>{
if(user)reject(new Error("User Exists"));
else{
resolve(SaveUser(username,password))// Is that ugly?
//what if I have one more repository logic here ?
//callback train...
}
})
.then(user => {
resolve(user);//If I do not want to resolve anything what is the best practice for it, like void functions?
}).catch(err=>{
console.log(err); // I catch the db error what will I do now :)
reject(err);// It seems not good way to handle it, isn't it ?
// Upper promise will handle that too. But I dont know how can I deal with that.
});;
});
}
repository.js
function GetUser(username) {
return new Promise((resolve, reject) => {
if (username === "existUser")
resolve("existUser");
else resolve("");
});
}
function SaveUser(username, password) {
return new Promise((resolve, reject) => {
reject(new Error("There is a problem with db"));//assume we forgot to run mongod
});
}
The code above seems awful to me.
I thought I need to define some method that can chain after GetUser method.
like
GetUser(username)
.then(SaveUserRefined)// how could it know other parameters like password, etc
.then(user=> {resolve(user)}) // resolve inside then ? confusing.
.catch(er=>//...);
I feel I do anti-pattern here and create "promise hell"
How could a simple flow like that implemented.
Validate username and save it.
Thanks.
Yes, that's the Promise constructor antipattern! Just write
function registerUser(username, password) {
return getUser(username).then(user => {
if (user) throw new Error("User Exists");
else return saveUser(username,password)
});
}
Notice I also lowercased your function names. You should only capitalise your constructor functions.
Should I catch error here or inner scope where registerUser implemented?
You should catch errors where you can handle them - and you should handle errors at the end of the chain (by logging them, usually). If registerUser doesn't provide a fallback result, it doesn't need to handle anything, and it usually also doesn't need to log the error message on its own (if you want to, see here for an example).
See also Do I always need catch() at the end even if I use a reject callback in all then-ables?.
If you already are working with promises, then there's no need to create your own. When you call .then on a promise and provide a function saying what to do, that creates a new promise, which will resolve to whatever the function returns.
So your registerUser function should look something like this:
function registerUser(username, password) {
return getUser(username)
.then(user => {
if (user) {
throw new Error('User Exists');
}
return saveUser(username, password)
});
}
and you use it like this:
registerUser('starfox', 'doABarrelRoll')
.catch(error => console.log(error);
Note that if SaveUser causes an error, it will end up in this final .catch, since i didn't put any error handling inside registerUser. It's up to you to decide where you want to handle the errors. If it's something recoverable, maybe registerUser can handle it, and the outside world never needs to know. But you're already throwing an error if the user name exists, so the caller will need to be aware of errors anyway.
Additionally your getUser and saveUser functions might also be able to avoid creating their own promises, assuming the real implementation calls some function that returns a promise.
Your should use async/await syntax to avoid Promise Hell.
Change your code like this
/**
* This is a promise that resolve a user or reject if GetUser or SaveUser reject
*/
async function RegisterUser (username, password) {
var user = await GetUser(username)
if (user)
return user;
var userSaved = await SaveUser(username,password)
return userSaved
}
If you use RegisterUser inside a async function just code
async function foo () {
try {
var usr = await RegisterUser('carlos', 'secret123')
return usr
} catch (e) {
console.error('some error', e)
return null
}
}
Or if you use it like a promise
RegisterUser('carlos', 'secret123')
.then(usr => console.log('goood', usr))
.catch(e => console.error('some error', e))

Categories

Resources