Jest Error: Matcher error: received value must be a promise - jestjs

I am writing unit tests for my NestJS application. However, I am getting into this error which I don't get how I can resolve. I am resolving the Promise as well.
My update function in service.ts looks like this:
async update(uname: string, updateUserDto: UpdateUserDTO): Promise<Users> {
const updateUser = await prisma.users.findUnique({
where: {
username: uname,
},
});
if (!updateUser || updateUser.username !== uname) {
throw new NotFoundException('User not found');
}
await prisma.tasks.updateMany({
where: {
userId: uname,
},
data: {
userId: uname,
},
});
return prisma.users.update({
where: {
username: uname,
},
data: {
...updateUserDto,
},
});
}
And my testing file looks like this.
controller.spec.ts:
describe('Update a user', () => {
it('should update the username of a user', async () => {
const updatedUser: UpdateUserDTO = {
name: 'Hulk',
};
await expect(
controller.update('wolverine', updatedUser)
).resolves.toEqual({
username: 'wolverine',
...updatedUser,
});
});
});
});
Update mock in controller.spec.ts is like this:
update: jest
.fn()
.mockImplementation((username: string, user: UpdateUserDTO) =>
Promise.resolve({ username, ...user })
),
And my controller where I am using update is, controller.spec.ts
#Patch(':uname')
update(#Param('uname') uname: string, #Body() updateUserDto: UpdateUserDTO) {
return this.usersService.update(uname, updateUserDto);
}

Related

Axios Patch Request showing params as undefined in the backed

I want to update data in the server with a Axios Patch call and there seems to be some error in the code which i cant seem to figure out. Help me out please.
Node express API call in the backend:
router.patch('/up',async (req, res) => {
try{
const id = req.query.userid; // User objectid here.
console.log(req.body)
console.log(req.query.userid)
const result= User.findById(id, (error, foundUser) => {
if (foundUser) {
const { products } = foundUser;
const filteredproducts = products.filter((item) => {
return item._id!= req.body.body._id; //product array object id
});
while (foundUser.products.length > 0) {
foundUser.products.pop();
}
foundUser.products = filteredproducts;
foundUser.products.push({
brandname: req.body.brandname,
productname: req.body.productname,
quantity: req.body.quantity,
price: req.body.price,
description: req.body.description
});
foundUser.save();
}
else
{
console.log('User not found')
}
})
res.status(200).send("update successfull");
}
catch(err){
res.status(403).send('update unsuccessfull')
console.log(err)
}
})
Userschema in db:
const userschema = new mongoose.Schema({
username: {
type: String,
},
email: {
type: String,
required: true
},
place: {
type: String,
required: true
},
password: {
type: String,
required: true
},
products:[
{
brandname: {
type:String,
required:true
},
productname:{
type:String,
required:true
},
quantity:{
type:Number,
required:true
},
price:{
type:Number,
required:true
},
description:{
type:String,
required:true
}
}
]
,
tokens:
[{
token: {
type: String,
required: true
}
}
]
})
const User = mongoose.model('USER', userschema)
Front end API code
const updateproduct = async(req,res) => {
console.warn(values.brandname, values.productname,values.quantity, values.price,
values.description)
console.warn("userlogin is:,",userid) // User object id here.
const _id=id; // Product object id here
await axios.patch(`http://localhost:5000/up`,{
headers: {
'Content-type': 'application/json'
},
data: {brandname,productname,quantity,price,description,_id}, //getting the values of all the fields here. checked by console logging them.
params:{
'userid':userid
}
},
{ withCredentials: true })
.then((res) => {
console.log(res)
navigate('/listproduct')
})
.catch((err) => {
console.log(err)
})
}
Sorry if my code look really bad. I am new to React and node.
All i wanna do is edit the product details of a product by the product objectid and store it in the db.
Any other method other than this is also fine. I am stuck with this problem for more than 2 days coudn't find a solution till now. Any help is much Appreciated.
EDIT:
I have tried to update the code but still the same errors.
backend API
router.patch('/up',async (req, res) => {
try{
const id = req.query.userid;
console.log("user id is",id)
console.log("req.body",req.body.body.brandname)
const result= User.findById(id, (error, foundUser) => {
if (foundUser) {
const { products } = foundUser;
const filteredexpenses = products.filter((item) => {
return item._id!= req.body.body._id;
});
while (foundUser.products.length > 0) {
foundUser.products.pop();
}
foundUser.products = filteredexpenses;
foundUser.products.push({
brandname: req.body.body.brandname,
productname: req.body.body.productname,
quantity: req.body.body.quantity,
price: req.body.body.price,
description: req.body.body.description
});
foundUser.save();
}
else
{
console.log('User not found')
}
})
res.status(200).send("update successfull");
}
catch(err){
res.status(403).send('update unsuccessfull')
console.log(err)
}
})
Front end API
const updateproduct = async(req,res) => {
console.warn(values.brandname, values.productname,values.quantity, values.price, values.description)
console.warn("userlogin is:,",userid)
const _id=id;
await axios.patch(`http://localhost:5000/up`,{
headers: {
'Content-type': 'application/json'
},
body:
{'brandname':brandname,'productname':productname,'quantity':quantity,'price':price,'description':description,'_id':_id},
params:{
'userid':userid
}
},
{ withCredentials: true })
.then((res) => {
console.log(res)
navigate('/listproduct')
})
.catch((err) => {
console.log(err)
})
}
Result on console log is
User id is undefined
req.body adidas //brandname
User not found
Try to change your axios request passing all parameters in the body like this:
await axios
.patch(
`http://localhost:5000/up`,
{ brandname, productname, quantity, price, description, _id, userid },
{
headers: {
'Content-type': 'application/json',
},
},
{ withCredentials: true }
)
.then((res) => {
console.log(res);
navigate('/listproduct');
})
.catch((err) => {
console.log(err);
});
And handle the end point like this:
router.patch('/up', async (req, res) => {
try {
const id = req.body.userid;
User.findById(id, async (error, foundUser) => {
if (foundUser) {
const { products } = foundUser;
const { brandname, productname, quantity, price, description, _id } =
req.body;
const filteredexpenses = products.filter((item) => {
return item._id != _id;
});
while (foundUser.products.length > 0) {
foundUser.products.pop();
}
foundUser.products = filteredexpenses;
foundUser.products.push({
brandname,
productname,
quantity,
price,
description,
});
await foundUser.save();
res.status(200).send('update successfull');
} else {
console.log('User not found');
res.status(400).send('User not found');
}
});
} catch (err) {
res.status(403).send('update unsuccessfull');
console.log(err);
}
});

How to test login with Bcrypt in Express with Mocha [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed last year.
Improve this question
I have implemented a login with hashed password authentication via Bcrypt in my Express Backend. Registration and login, as well as authenticated routes, work fine in the app, but not in the test environment. The following registration test succeeds, but the login test fails. When I log the password inside the user model's compare function, it is logged as plain text instead of the hash. I don't understand why though.
Any help is appreciated.
This is the users.test.js:
import { expect, server, BASE_URL } from './setup'
describe('User', () => {
it('registers a user', (done) => {
const data = { email: 'chester#benning.ton', name: 'chester', password: 'intheend' }
server
.post(`${BASE_URL}/users`)
.send(data)
.expect(201)
.end((err, res) => {
expect(res.status).to.equal(201)
expect(res.body).to.have.property('user')
expect(res.body.user).to.have.property('id')
expect(res.body.user).to.have.property('name', data.name)
done()
})
})
it('logs in a user', (done) => {
const data = { name: 'chester', password: 'intheend' }
server
.post(`${BASE_URL}/users/login`)
.send(data)
.expect(201)
.end((err, res) => {
expect(res.status).to.equal(201)
expect(res.body.user).to.have.property('id')
done()
})
})
})
The user model looks like this:
const { Model, Op } = require('sequelize')
const bcrypt = require('bcrypt')
const config = require('../../config/config')
function hashPassword(user) {
const saltRounds = config.saltRounds
if (!user.changed('password')) {
return false
}
return bcrypt.hash(user.password, saltRounds).then((hashedPassword) => {
user.setDataValue('password', hashedPassword)
})
}
module.exports = (sequelize, DataTypes) => {
class User extends Model {}
User.init({
email: {
allowNull: false,
type: DataTypes.STRING,
unique: true,
validate: {
isUnique(value, next) {
User.findOne({
where: {
email: {
[Op.iLike]: value.trim()
}
}
}).then((user) => {
if (user) {
return next('E-Mail is already registered')
}
return next()
}).catch(() => next('Could not be validated'))
}
}
},
name: {
allowNull: false,
type: DataTypes.STRING,
unique: true,
validate: {
isUnique(value, next) {
User.findOne({
where: {
name: {
[Op.iLike]: value.trim()
}
}
}).then((user) => {
if (user) {
return next('User name already exists')
}
return next()
}).catch(() => next('Could not be validated'))
}
}
},
password: {
allowNull: false,
type: DataTypes.STRING
}
}, {
hooks: {
beforeCreate: hashPassword,
beforeSave: hashPassword,
beforeUpdate: hashPassword
},
modelName: 'User',
sequelize,
tableName: 'Users'
})
User.prototype.comparePassword = async function comparePassword(password) {
return bcrypt.compare(password, this.password).then((res) => {
return res
}).catch((err) => {
return false
})
}
User.prototype.toJSON = function toJSON() {
const userObj = { ...this.get() }
delete userObj.password
return userObj
}
return User
}
And this is my UserController.js:
const { Op } = require('sequelize')
const jwt = require('jsonwebtoken')
const { User } = require('../models')
const config = require('../../config/config')
function jwtSignUser(user) {
const ONE_DAY = 60 * 60 * 24
return jwt.sign(user, config.authentication.jwtSecret, {
expiresIn: ONE_DAY
})
}
module.exports = {
findAll(req, res) {
const options = {
attributes: {
exclude: [ 'password' ]
},
order: [
[ 'name', 'ASC' ]
]
}
return User.findAll(options).then((users) => res.status(200).send(users)).catch(() => {
res.status(404).send({
message: 'Could not find users'
})
})
},
login(req, res) {
const { name, password } = req.body
User.findOne({
where: {
name: {
[Op.iLike]: name
}
}
}).then(async (user) => {
if (!user) {
return res.status(422).send({
message: 'Login information incorrect'
})
}
const passwordIsValid = await user.comparePassword(password)
if (!passwordIsValid) {
return res.status(422).send({
message: 'Login information incorrect'
})
}
const userJson = user.toJSON()
return res.status(201).send({
user: userJson,
token: jwtSignUser(userJson)
})
}).catch(() => {
res.status(500).send({
message: 'Login was not successful'
})
})
},
register(req, res) {
return User.create(req.body).then((user) => {
const userJson = user.toJSON()
return res.status(201).send({
user: userJson,
token: jwtSignUser(userJson)
})
}).catch((error) => {
res.status(422).send({
message: error.errors[0].message
})
})
}
}

How to write unit tests for sequelize models with Chai

My tests are working and passing, but chai's done function is messing up. I've tried a few different methods, but I don't understand where I'm going wrong in these unit tests. I'm very new to unit testing and chai, so any help would be greatly appreciated
The places where the test is failing: line 40 (create), line 98 (update)
Any ideas?
const chai = require('chai')
let should = chai.should()
let expect = chai.expect
let db = require('../app/models')
db.hosts.modelName = 'Hosts'
db.victims.modelName = 'Victims'
let models = [db.hosts, db.victims]
models.forEach(model => {
describe(`${model.modelName} Model`, function (done) {
var modelData = {
guest_count: 3,
start_date: "2018-01-11T00:00:00.000Z",
end_date: "2018-01-12T00:00:00.000Z",
location: {
type: "Point",
coordinates: [
-74.323564,
40.232323
]
},
first_name: "Sean",
last_name: "Destroyed",
phone: "7325556677",
address: "123 main street, red bank, nj",
email: "test#gmail.com",
}
it(`should create a new ${model.modelName}`, function () {
model.create(modelData).then(function (user) {
//victim name should be equivalent to the fake submission we are using
expect(user.first_name).to.equal("Sean");
//remove the entry from the database
model.destroy({
where: {
id: user.id
}
})
done()
})
});
it(`should delete a ${model.modelName} from the database`, function () {
model.create(modelData).then(function (user) {
//victim name should be equivalent to the fake submission we are using
//remove the entry from the database
model.destroy({
where: {
id: user.id
}
})
try {
model.findOne({
where: {
id: user.id
}
})
} catch (err) {
expect(user.first_name).to.undefined;
if (err) {
done()
}
}
})
})
it(`should update the ${model.modelName} entry in the database`, function () {
model.create(modelData).then(function (user) {
//after user is created, then update a value
modelData.guest_count = 12
model.update(modelData, {
where: {
id: user.id
}
}).then(function(data) {
model.findOne({
where: {
id: user.id
}
}).then(function (data) {
expect(data.guest_count).to.equal(12);
}).then(function () {
model.destroy({
where: {
id: user.id
}
})
}).then(function() {
done()
})
})
})
})
})
});
There are two things to keep in mind:
(1) Sequelize uses promises for its ORM methods. So, even after you are calling destroy, you need to attach a callback, e.g.:
model.destroy({
where: {
id: user.id
}
})
.then(function() {
// now do something
});
(2) The done method in chai should be attached to each test, as opposed to the test block:
describe('some test block', function() {
it('should do something,' function(done) {
User.findAll().then(function(users) {
// expect users to do something
done(); // tests are done
});
});
});
In your case, here are the two failing test cases:
// ensure "destroy" has a callback
it(`should create a new ${model.modelName}`, function (done) {
model.create(modelData).then(function (user) {
//victim name should be equivalent to the fake submission we are using
expect(user.first_name).to.equal("Sean");
//remove the entry from the database
model.destroy({
where: {
id: user.id
}
}).then(function() {
done();
})
})
});
// update
it(`should update the ${model.modelName} entry in the database`, function () {
model.create(modelData).then(function (user) {
//after user is created, then update a value
modelData.guest_count = 12
model.update(modelData, {
where: {
id: user.id
}
}).then(function(data) {
model.findOne({
where: {
id: user.id
}
}).then(function (data) {
expect(data.guest_count).to.equal(12);
}).then(function () {
model.destroy({
where: {
id: user.id
}
}).then(function() {
done()
})
})
})
})
})
#mcranston18 left a really nice detailed accepted answer.
What I'd like to add for others that find the question or for OP in the future, is the use of async/await:
describe('some test block', function() {
it('should do something', async function() { // notice async and no done
const users = await User.findAll()
// expect users.to (...)
})
})
Here's a really simple way to create then update using async/await:
describe('some test block', function () {
it('should do something', async function () {
const Joe = await User.create({ name: 'Jo' }) // oops
// assertions/expect/should
// ex: expect(Joe.name).to.equal('Jo')
await Joe.update({ name: 'Joe' }) // that's better
// assertions/expect/should
// ex: expect(Joe.name).to.equal('Joe')
})
})

The loader.load() function must be called with a value,but got: undefined

I am following this graphql tutorial, everything was going ok until I try to use dataloaders.
My server.js is:
const start = async () => {
const mongo = await connectMongo();
const buildOptions = async req => {
const user = await authenticate(req, mongo.Users);
return {
context: {
dataloaders: buildDataloaders(mongo),
mongo,
user
},
schema
};
};
app.use('/graphql', bodyParser.json(), graphqlExpress(buildOptions));
app.use(
'/graphiql',
graphiqlExpress({
endpointURL: '/graphql',
passHeader: `'Authorization': 'bearer token-name#email.com'`
})
);
app.use('/', expressStaticGzip('dist'));
app.use('/attendance', expressStaticGzip('dist'));
app.use('/login', expressStaticGzip('dist'));
spdy.createServer(sslOptions, app).listen(process.env.PORT || 8080, error => {
if (error) {
console.error(error);
return process.exit(1);
} else {
console.info(
`App available at https://localhost:${process.env.PORT || 3000}`
);
}
});
};
My copy and paste dataloaders.js:
const DataLoader = require('dataloader');
async function batchUsers(Users, keys) {
return await Users.find({ _id: { $in: keys } }).toArray();
}
module.exports = ({ Users }) => ({
userLoader: new DataLoader(keys => batchUsers(Users, keys), {
cacheKeyFn: key => key.toString()
})
});
And my resolvers.js:
export default {
Query: {
allLinks: async (root, data, { mongo: { Links } }) =>
Links.find({}).toArray()
},
Mutation: {
createLink: async (root, data, { mongo: { Links }, user }) => {
const newLink = Object.assign({ postedById: user && user._id }, data);
const response = await Links.insert(newLink);
return Object.assign({ id: response.insertedIds[0] }, newLink);
},
createUser: async (root, data, { mongo: { Users } }) => {
const newUser = {
name: data.name,
email: data.authProvider.email.email,
password: data.authProvider.email.password
};
const response = await Users.insert(newUser);
return Object.assign({ id: response.insertedIds[0] }, newUser);
},
signinUser: async (root, data, { mongo: { Users } }) => {
const user = await Users.findOne({ email: data.email.email });
if (data.email.password === user.password) {
return { token: `token-${user.email}`, user };
}
}
},
Link: {
id: root => root._id || root.id,
postedBy: async ({ postedById }, data, { dataloaders: { userLoader } }) => {
return await userLoader.load(postedById);
}
},
User: {
id: root => root._id || root.id
}
};
When I try get my allLinks I got the error:
TypeError: The loader.load() function must be called with a value,but
got: undefined.
Can anyone help me?
So I was able to reproduce the error by creating a link with a user, deleting the user from the Mongo database, and then querying for the postedBy attribute of the Link.
I would suggest dropping all your links and recreating your user (register + sign in), creating a new link, then querying for the postedBy field.

Mocha Thinks Resolution method is overspecified. Specify a callback *or* return a Promise; not both

This is my first day with TDD.
var mongoose = require("mongoose"),
should = require('should'),
User = require("app/models/user");
mongoose.connect('mongodb://localhost/altor-security');
describe('user data', function() {
it('password should be different after changing password', function(done) {
var old_password_hash,
new_password = "12345678";
return User.findOne({ email: "example#gmail.com" }).exec()
.then(function(user) {
old_password_hash = user.password;
return User.findOneAndUpdate({ _id : user._id }, { password: new_password }, { new: true }).exec();
})
.then(function(user) {
user.password.should.not.equal(old_password_hash);
done();
})
.catch(function(err) {
err.should.equal(null);
done();
})
});
})
My test fails because it thinks that User.findOneAndUpdate method is over specified. But it does take three parameters, findCommand, update, and options.
Any ideas why it's failing though?
Thanks
My test fails because it thinks that User.findOneAndUpdate method is over specified.
Actually, no. It says that "resolution method" (ie the way your code tells mocha async test is done) is over specified.
You are using callback and returning a Promise so mocha can't tell when your test is done and if it is ok.
You need to either use done or return a promise. Not both.
Preferred way (return a Promise)
describe('user data', function() {
it('password should be different after changing password', function(/*do not use done*/) {
var old_password_hash,
new_password = "12345678";
// return a Promise
return User.findOne({ email: "example#gmail.com" }).exec()
.then(function(user) {
old_password_hash = user.password;
return User.findOneAndUpdate({ _id : user._id }, { password: new_password }, { new: true }).exec();
})
.then(function(user) {
user.password.should.not.equal(old_password_hash);
})
});
})
OR
describe('user data', function(done) {
it('password should be different after changing password', function(done) {
var old_password_hash,
new_password = "12345678";
// do not return anything
User.findOne({ email: "example#gmail.com" }).exec()
.then(function(user) {
old_password_hash = user.password;
return User.findOneAndUpdate({ _id : user._id }, { password: new_password }, { new: true }).exec();
})
.then(function(user) {
user.password.should.not.equal(old_password_hash);
done();
})
.catch(function(err) {
err.should.equal(null);
done();
})
});
})

Resources