Stubbing function with jest to test Internal error - node.js

I'm trying to test a controller function using jest, and i want to test all three status return
const messagesSender = async (req, res) => {
try {
const { message } = req.body;
if (!message) {
return res.status(400).send({ message: 'Message cannot be null' });
}
return res.status(200).send(message);
} catch (error) {
return res.status(500).json({ error: 'Internal Error' });
}
};
module.exports = { messagesSender };
Test file:
const messages = require('../controller/messagesController');
describe('Testing Messages Controller', () => {
it('should return internal error', async () => {
const req = {
body: {
message: 'testing',
},
};
const res = {
send: jest.fn(),
status: jest.fn(() => res),
};
const messageResponse = await messages.messagesSender(req, res);
messageResponse.mockImplementation(() => {
throw new Error('User not found');
});
expect(res.status).toBeCalledWith(500);
});
});
But i'm receiving the error:
TypeError: Cannot read property 'mockImplementation' of undefined
How can i fix this and test the 500 result?

it('should return internal error', async () => {
const req = {
body: {
message: 'testing',
},
};
const res = {
send: jest.fn().mockImplementation(() => {
throw new Error('User not found');
}),
status: jest.fn(() => res),
};
await messages.messagesSender(req, res);
expect(res.status.mock.calls[1][0]).toBe(500);
});
In your case send function returning nothing and causing this problem. In this case status method have been called twice, so you need to check the second call.

Related

Sinon spy return "undefined"

I'm on node.js using Mongoose model and testing with Mocha with Sinon.
The function I'm testing:
exports.findAllUsers = (req, res) => {
UserCollection.find()
.then(users => {
res.status(500)
.send(users)
})
.catch(err => {
res.status(500)
.send({message: err.message || "error occurred retriving users informations"})
})
Here my test code:
describe("findAllUsers", () => {
const sandbox = sinon.createSandbox();
afterEach(function () {
sinon.restore();
sandbox.restore();
});
const req = {
params: {
id: new mongoose.Types.ObjectId(),
},
};
const statusJsonSpy = sinon.spy();
const res = {
send: sinon.spy(),
status: sinon.stub().returns({ json: statusJsonSpy }),
};
it("should return all users data if found", async () => {
mongoose.Model.find = sandbox
.stub()
.returns(Promise.resolve("users data"));
await findAllUsers(req, res);
console.log(res.send.callCount);
expect(res.send).to.have.been.calledWith("users data");
});
res.send.callCount return 0, res.send is never been called thus the test fail!
If I edit my function as in the following, the test work:
exports.findAllUsers = (req, res) => {
UserCollection.find()
.then(users => {
res.status(500)
res.send(users) // added res, not using concatenation
})
.catch(err => {
res.status(500)
.send({message: err.message || "error occurred retriving users informations"})
})
}

How to return a custom message instead of an empty array from a Mongoose Find query when no results are found

I'm trying to return a custom message when I do a mongoose.find query and it doesn't find anything on the system.
At the moment I have the following and it works, but I was wondering if there is a better way of accomplishing what I'm doing .
// <status> => one of ["valid", "expired", "attributed"]
const getByStatus = (status) => {
return new Promise((resolve, reject) => {
PhotoCodes.find({ status: status })
.then((response) => {
resolve(response)
})
.catch((error) => {
reject(error)
})
})
}
router.post('/byStatus', (req, res, next) => {
const { status } = req.body
photoCodesModel
.getByStatus(status)
.then((response) => {
if (response.length == 0 ) {
res.status(404).send('Not Found') // [ASK]: If response is an empty "[]" treat it as nothing was found. Is there a better way of doing this?
} else {
res.status(200).json({ result: 'ok', data: response })
}
})
.catch((error) => {
res.status(400).json({ result: 'ok', error: error })
})
})
You can use async/await to do, like this
const getByStatus = async (status) => {
const photo = await PhotoCodes.find({ status: status })
return photo .
}
router.post('/byStatus',async (req, res, next) => {
const status = req.body.status;
try {
const photo = await getByStatus(status)
if(photo.length!==0) {
return res.status(200).json({ result: 'ok', data: photo })
}
return res.status(404).json({message : "treat it as nothing was found"})
}
catch(error) {
return res.status(400).json({ result: 'ok', error: error })
}
});

I'm getting a weird error while using "next-connect" in my nextJS project that had fixed itself in dev but is now even worse in prod

As i said in the title i am using a npm package called "next-connect" to structure my api. Every api route that i created suffered from this error. This is the error :
Unhandled rejection: TypeError: Cannot read property 'end' of undefined
at next (/var/task/node_modules/next-connect/lib/index.js:43:54)
at next (/var/task/node_modules/next-connect/lib/index.js:49:9)
at next (/var/task/node_modules/next-connect/lib/index.js:58:16)
at next (/var/task/node_modules/next-connect/lib/index.js:49:9)
at next (/var/task/node_modules/next-connect/lib/index.js:58:16)
at next (/var/task/node_modules/next-connect/lib/index.js:58:16)
at next (/var/task/node_modules/next-connect/lib/index.js:60:9)
at <anonymous>
at process._tickDomainCallback (internal/process/next_tick.js:228:7)
After a few minutes of trying i get to solve it in dev. Most of this due to messing with the .env file.
Here the code from my API Route :
import nextConnect from "next-connect";
import bcrypt from "bcryptjs";
import middleware from "../../middlewares/middleware";
const handler = nextConnect();
handler.use(middleware);
handler.get((req, res) => {
if (req.user) {
const { name, email, bio, profilePicture, emailVerified } = req.user;
return res.status(200).send({
status: "ok",
data: {
isLoggedIn: true,
user: {
name,
email,
bio,
profilePicture,
emailVerified
}
}
});
}
return res.status(200).send({
status: "ok",
data: {
isLoggedIn: false,
user: {}
}
});
});
handler.post((req, res) => {
const { email, password } = req.body;
return req.db
.collection("users")
.findOne({ email })
.then(user => {
if (user) {
return bcrypt.compare(password, user.password).then(result => {
if (result) return Promise.resolve(user);
return Promise.reject(Error("The password you entered is incorrect"));
});
}
return Promise.reject(Error("The email does not exist"));
})
.then(user => {
req.session.userId = user._id;
return res.send({
status: "ok",
message: `Welcome back, ${user.name}!`
});
})
.catch(error =>
res.send({
status: "error",
message: error.toString()
})
);
});
handler.delete((req, res) => {
delete req.session.userId;
return res.status(200).send({
status: "ok",
message: "You have been logged out."
});
});
export default handler;
And here code from the next-connect package (the one mentioned in the error report) :
module.exports = () => {
function connect(req, res) {
connect.handle(req, res);
}
connect.stack = [];
function add(method, ...handle) {
for (let i = 0; i < handle.length; i += 1) {
if (handle[i].stack) Object.assign(this.stack, handle[i].stack);
else this.stack.push({ handle: handle[i], method });
}
}
// method routing
connect.get = add.bind(connect, 'GET');
connect.head = add.bind(connect, 'HEAD');
connect.post = add.bind(connect, 'POST');
connect.put = add.bind(connect, 'PUT');
connect.delete = add.bind(connect, 'DELETE');
connect.options = add.bind(connect, 'OPTIONS');
connect.trace = add.bind(connect, 'TRACE');
connect.patch = add.bind(connect, 'PATCH');
// middleware
connect.use = add.bind(connect, '');
connect.error = add.bind(connect, 'ERR');
connect.apply = function apply(req, res) {
return new Promise((resolve) => this.handle(req, res, resolve));
};
connect.handle = function handle(req, res, done) {
let idx = 0;
const { stack } = this;
async function next(err) {
const layer = stack[idx];
idx += 1;
// all done
if (!layer) {
if (done) done();
else if (!res.headersSent) res.writeHead(404).end();
return;
}
// check if is correct method or middleware
if (layer.method !== '' && layer.method !== 'ERR' && layer.method !== req.method) {
next(err);
return;
}
try {
if (!err) { await layer.handle(req, res, next); return; }
// there is an error
if (layer.method === 'ERR' || layer.handle.length === 4) {
await layer.handle(err, req, res, next);
} else next(err);
} catch (error) {
next(error);
}
}
// Init stack chain
next();
};
return connect;
};

Not getting correct status code (409) if email exists using Next.js, Mongoose, MongoDb Atlas and Express

I am building a login/Register portion of my app. Right now I'm using express-validator to check if an email exists in my collection.
This is my route:
var router = require('express').Router()
var UserModel = require('../models/UserModel')
var { body } = require('express-validator');
router
.route('/registration')
.get(function(req, res) {
console.log(0)
UserModel.find({}, (err, users) => {
console.log(1);
if (err) return res.status(500).send(err)
console.log(2);
return res.json(users);
})
})
.post(body('username_email').custom(value => {
console.log("value ", value);
console.log(3)
UserModel.findOne({ 'username_email': value }, (err) => {
console.log(4);
if (err) return res.status(409).send(err);
})
}), async(req, res, next) => {
console.log(5)
try {
let newUser = new UserModel(req.body);
let savedUser = await newUser.save();
console.log(6);
if (savedUser) return res.redirect('/users/registration?success=true');
return next(new Error('Failed to save user for unknown reasons'))
} catch (err) {
return next(err)
}
})
module.exports = router
In my component on the front end I have a function in my fetch which will catch the error if there is one.
handleErrors(response) {
if (!response.ok) {
console.log('This email exists!')
throw Error(response.statusText);
}
return response;
}
handleSubmit(event) {
event.preventDefault()
var { username, password } = this.state
var mailFormat = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
var error = false
if (!username.match(mailFormat)) {
this.setState({ usernameError: true })
error = true
} else {
this.setState({ usernameError: false })
}
if (password.length <= 8) {
this.setState({ passwordError: true })
error = true
} else {
this.setState({ passwordError: false })
}
console.log(`error ${error}`)
if (error == false) {
this.setState({ formError: false, formSuccess: true })
}
window.fetch('http://localhost:8016/users/registration', {
method: 'POST',
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
body: JSON.stringify({ username_email: username, password: password })
})
.then(this.handleErrors)
.then(function (response) {
console.log(`response ${response}`)
return response.json()
}).then(function (data) {
console.log('User created:', data)
}).catch(function (error) {
console.log(error);
});
}
The console.log in the fetch, handleErrors is being registered in the console, but why isn't the error status a 409 like I indicated.
Closer excerpt of post route!
.post(body('username_email').custom(value => {
console.log("value ", value);
console.log(3)
Is this the problem? Node style should have a error and callback?
UserModel.findOne({ 'username_email': value }, (err) => {
console.log(4);
if (err) return res.status(409).send(err);
})
}), async(req, res, next) => {
console.log(5)
try {
let newUser = new UserModel(req.body);
let savedUser = await newUser.save();
console.log(6);
if (savedUser) return res.redirect('/users/registration?success=true');
return next(new Error('Failed to save user for unknown reasons'))
} catch (err) {
return next(err)
}
})
UPDATE
I tried Nick's solution but I get this:
MongoError: E11000 duplicate key error collection: development.users index: email_1 dup key: { : null }
at Function.create (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/mongodb-core/lib/error.js:43:12)
at toError (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/mongodb/lib/utils.js:149:22)
at coll.s.topology.insert (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/mongodb/lib/operations/collection_ops.js:859:39)
at handler (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/mongodb-core/lib/topologies/replset.js:1155:22)
at /Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/mongodb-core/lib/connection/pool.js:397:18
at process._tickCallback (internal/process/next_tick.js:61:11)
POST /users/registration 500 312.485 ms - 51
^C
Two things I am noticing:
I get back MongoError: E11000 duplicate key error collection: development.users index: email_1 dup key: { : null }
which is the error from having a duplicate email, but number one where is E-mail already in use message in the console from the promise? And two how can I pass the error status "res.status(409).send(err);" from the promise?
The issue was that during your validation, you weren't returning the promise since the mongoose call is async. The rest the code ran before your validator was finished. I commented where you were missing the return.
router.route('/registration')
.get(function(req, res) {
UserModel.find({}, (err, users) => {
if (err) res.status(500).send(err)
res.json(users)
})
})
.post(body('username').custom(value => {
return UserModel.findOne({ 'email': value }).then(user => { // Return Promise
if (user) {
return Promise.reject('E-mail already in use');
}
});
}), async(req, res, next) => {
try {
let newUser = new UserModel(req.body)
let savedUser = await newUser.save(err => {
if (err) return res.json({ success: false, error: err })
return res.json({ success: true })
})
if (savedUser) return res.redirect('/users/registration?success=true');
return next(new Error('Failed to save user for unknown reasons'))
} catch (err) {
return next(err)
}
})
module.exports = router
UPDATE
Just read through express-validator docs. I think you would need to validate the errors during the request process
var router = require('express').Router()
var UserModel = require('../models/UserModel')
var { body, validationResult } = require('express-validator');
router.route('/registration')
.get(function(req, res) {
UserModel.find({}, (err, users) => {
if (err) res.status(500).send(err)
res.json(users)
})
})
.post(body('username').custom(value => {
return UserModel.findOne({ 'email': value }).then(user => { // Return Promise
if (user) {
return Promise.reject('E-mail already in use');
}
});
}), async(req, res, next) => {
// Checks for errors in validation
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
try {
let newUser = new UserModel(req.body)
let savedUser = await newUser.save(err => {
if (err) return res.json({ success: false, error: err })
return res.json({ success: true })
})
if (savedUser) return res.redirect('/users/registration?success=true');
return next(new Error('Failed to save user for unknown reasons'))
} catch (err) {
return next(err)
}
})
module.exports = router

Unable to thrown an error in Express Node

I have a following Post route
router.post("/message", async (req, res) => {
const newMessage = new Message({
From: req.body.sendersPhoneNumber,
To: 9188123560,
OTP: req.body.randomNumber,
Message: req.body.TextToSend
})
newMessage.save().then((response) => {
const from = "NEXMO"
const to = response.To
const text = response.Message
nexmo.message.sendSms(from, to, text, {type: 'unicode'}, (error, responseData) => {
if (error) {
console.log(error)
throw new Error (error)
} else {
console.dir(responseData)
res.send(responseData)
}
})
})
})
In this, I want to throw an error If there is an error for which i did something like this
if (error) {
console.log(error)
throw new Error (error)
}
But that doesn't seem to be working, can some help me by sharing, how can we throw an error in nodeJs
This is my request from frontend
axios.post(test_url_base + "/contact/message", this.state).then(response => {
Solution 01:
Line 1: Need a third parameter, next to pass the error to the next handler.
Line 16: pass the error to the next error handler, return next(error);
NB: In your root file like index.js or server.js or whatever you are using, You need an error handler, like,
app.use((err, req, res, next) => {
console.log('Found a error');
});
Your final code will be,
router.post("/message", async (req, res, next) => {
const newMessage = new Message({
From: req.body.sendersPhoneNumber,
To: 9188123560,
OTP: req.body.randomNumber,
Message: req.body.TextToSend
})
newMessage.save().then((response) => {
const from = "NEXMO"
const to = response.To
const text = response.Message
nexmo.message.sendSms(from, to, text, {type: 'unicode'}, (error, responseData) => {
if (error) {
console.log(error)
return next(error);
} else {
console.dir(responseData)
res.send(responseData)
}
})
})
})
Solution 2:
If you encounter an error, pass the error to the client side,
Use res.status(400).send(error);
In this case, your code should be,
router.post("/message", async (req, res, next) => {
const newMessage = new Message({
From: req.body.sendersPhoneNumber,
To: 9188123560,
OTP: req.body.randomNumber,
Message: req.body.TextToSend
})
newMessage.save().then((response) => {
const from = "NEXMO"
const to = response.To
const text = response.Message
nexmo.message.sendSms(from, to, text, {type: 'unicode'}, (error, responseData) => {
if (error) {
console.log(error)
return res.send(error);
} else {
console.dir(responseData)
res.send(responseData)
}
})
})
})
as you have done that is also correct way but i don't get why it is not working.
you can also try this one
if (error) {
return next(error)
}
or you can also do this
res.render('error', { error: err })
Put try catch block , so that any error thrown from try block gets caught in catch block.
router.post("/message", async (req, res) => {
try {
const newMessage = new Message({
From: req.body.sendersPhoneNumber,
To: 9188123560,
OTP: req.body.randomNumber,
Message: req.body.TextToSend
})
newMessage.save().then((response) => {
const from = "NEXMO"
const to = response.To
const text = response.Message
nexmo.message.sendSms(from, to, text, { type: 'unicode' }, (error, responseData) => {
if (error) {
console.log(error)
throw new Error(error)
} else {
console.dir(responseData)
res.send(responseData)
}
})
});
}
catch (e) {
res.send({ status: 400, error: e });
}
})

Resources