Stub Mongoose find method - node.js

I'm trying to stub the find or exec functions to test the following function:
function getOpenTickets() {
return Ticket.find({})
.populate('assignees', ['fullName', 'firstName', 'email', 'notificationSettings.dailyEmail'])
.populate('property', 'name')
.populate('type', 'title')
.populate({path: 'unit', model: 'Unit', select: 'title'})
.sort('created')
.lean()
.exec();
}
I found several posts about stubbing mongoose methods but none of them worked for me, here is what I have:
it('should test getOpenTickets', async() => {
findStub = sinon.stub(Ticket, 'find');
var result = await utils.__get__('getOpenTickets')();
findStub.restore();
});
But I get:
Cannot read property 'populate' of undefined
so I tried replacing it with a fake object:
var fakeFind = {
args: {
populate: [],
sort: null,
lean: null
},
populate: function (a) {
this.args.populate.push(a)
},
sort: function (a) {
this.args.sort = a;
},
lean: function () {
this.args.lean = true
},
exec: function () {
return Promise.resolve(this.args);
}
}
And
findStub = sinon.stub(Ticket, 'find').callsFake(fakeFind);
And the result is:
TypeError: this.fakeFn.apply is not a function
I've also tried stubbing mongoose.Model, prototype, exec, and some other stuff with no luck.
Any ideas?

Try to use sinon-mongoose https://github.com/underscopeio/sinon-mongoose
Here is an example:
require('sinon');
require('sinon-mongoose');
sinon.mock(Ticket)
.expects('find')
.chain('populate').withArgs(/* args */)
.chain('sort').withArgs('create')
.chain('lean')
.chain('exec')
.resolves('SOME_VALUE');

Related

How to access a different schema in a virtual method?

I want to write a virtual (get) method for my MongoDb collection (Parts) which needs to access a different schema: I want it to assert if a document is 'obsolete' according to a timestamp available in a different (Globals) collection:
const partsSchema = new Schema({
...
updatedAt: {
type: Date,
},
...
}, {
toObject: { virtuals: true },
toJSON: { virtuals: true },
});
partsSchema.virtual('obsolete').get(async function() {
const timestamp = await Globals.findOne({ key: 'obsolescenceTimestamp' }).exec();
return this.updatedAt < timestamp.value;
});
But when I do a find, I always get a {} in the obsolete field, and not a boolean value...
const p = await parts.find();
...
"obsolete": {},
...
Is there some way to accomplish my goal?
You can do this, but there are a few obstacles you need to hurdle. As #Mohammad Yaser Ahmadi points out, these getters are best suited for synchronous operations, but you can use them in the way you're using them in your example.
So let's consider what's happening here:
partsSchema.virtual('obsolete').get(async function() {
const timestamp = await Globals.findOne({ key: 'obsolescenceTimestamp' }).exec();
return this.updatedAt < timestamp.value;
});
Since the obsolete getter is an async function, you will always get a Promise in the obsolete field when you query your parts collection. In other words, when you do this:
const p = await parts.find();
You will get this:
...
"obsolete": Promise { <pending> },
...
So besides getting the query results for parts.find(), you also need to resolve the obsolete field to get that true or false result.
Here is how I would write your code:
partsSchema.virtual('obsolete').get(async function() {
const Globals = mongoose.model('name_of_globals_schema');
const timestamp = await Globals.findOne({ key: 'obsolescenceTimestamp' });
return this.updatedAt < timestamp.value;
});
Then when querying it...
parts.findOne({_id: '5f76aee6d1922877dd769da9'})
.then(async part => {
const obsolete = await part.obsolete;
console.log("If obsolete:", obsolete);
})

How to return more than two values from service to controller in node js

I have a controller/ user.js file to get data using a function from service/user.js
From getUsers function in service I can't return more than 1 value. for example I want to return return{User,pages}. But, i getting error while return like return{User,pages}
The below is thw error i getting while using return{User,pages}
userService.getUsers(...).then is not a function
controller/user.js
function getUsers(req, res){
userService.getUsers({
page_no:req.body.page_no,
previous:req.body.previous
}
)
.then(data => res.send(data));
};
service/user.js
const getUsers = function (data) {
let limit = 10; // number of records per page
let offset = 0;
let page = data.page_no; // page number
data = Users.findAll({where: {
DeletedAt: {
$eq: null
}
}})
const pages = Math.ceil(data.count / limit);
offset = limit * (page - 1);
const User = Users.findAll({
attributes: ['id', 'first_name', 'last_name', 'role_id','login','is_active'],
limit: limit,
order: [
['createdAt', 'DESC']
],
offset: offset,
$sort: { id: 1 },
where: {
DeletedAt: {
$eq: null
}
}
})
return User
};
Looks like you're using some ORM which returns a Promise.
When you return just User, the type of User is (probably, as you do not get error) a Promise so the function will return a promise and you can call .then method.
However when you return { User, pages }, you're not returning a promise but an Object and objects don't have then method, which is why you're getting the error.
When you return the object { User, pages }, you can change your code to extract the promise and call then method on it:
function getUsers(req, res){
const { User, pages } = userService.getUsers({
page_no:req.body.page_no,
previous:req.body.previous
})
// Call the then User which is a Promise
User.then(data => res.send(data))
};

How to test sequelize query (where) logic?

I'm trying to write unit tests which will test if the search query is right In other words if the logic written in where statement is returning expected results.
async function search(some_data){
return Event.findOne({
where: {
id: 123435,
[Op.or]: [
days: {
[Op.overlap]: some_data.days,
},
[Op.or]: [
{
startTime: {
[Op.gt]: some_data.start1,
},
endTime: {
[Op.lt]:some_data.end1,
},
},
{
startTime: {
[Op.lt]: some_data.start2,
[Op.lt]: some_data.end2,
},
endTime: {
[Op.gt]: some_data.end2,
[Op.gt]: some_data.start2,
},
},
],
],
},
})};
I need to test the result for different inputs.
I don't want to convert this test into integration test and use the original db, so I used sequelize-mock lib, but this returns only the result that I've defined and does not run the real query.
To test that your method is called with the correct parameters, you will need to use dependency injetion and a library to "spy" on your findOne method. In the example below, I am using Sinon
// app.js
// Note that "Event" must be used an argument in order to mock it out
async function search(Event, some_data) {
return Event.findOne({
where: {
id: 123435
}
})
}
If your test file:
// your test file
const app = require('./app');
const sinon = require('sinon');
const EventMock = {
findOne: sinon.spy()
};
describe('search', () => {
it('should call with the right parameters', () => {
const some_data = {};
search(EventMock, some_data);
assert(EventMock.findOne.calledWith({
where: {
id: 123435
}
}))
});
});

Query using sequelizejs returns undefined

A model.js file contains this model :
exports.Conversations = db.sequelize.define('conversations', {
room_id: {
type: db.Sequelize.STRING
},
user_id: {
type: db.Sequelize.STRING
},
friend_id: {
type: db.Sequelize.STRING
},
}, {
timestamps: true,
createdAt:'created_at',
updatedAt:'updated_at',
deletedAt:'deleted_at',
freezeTableName: true // Model tableName will be the same as the model name
});
In query.js I have the following function :
exports.checkRoom = function(user_id,friend_id) {
models.Conversations.findOne({ where: { $or: [{user_id: user_id , friend_id: friend_id}, {user_id: friend_id , friend_id: user_id}] }} ).then(function(conversation) {
return conversation;
});
}
equivalent to:
SELECT
"id", "room_id", "user_id", "friend_id", "created_at", "updated_at"
FROM
"conversations" AS "conversations"
WHERE
(("conversations"."user_id" = '127' AND "conversations"."friend_id" = '124')
OR ("conversations"."user_id" = '124' AND "conversations"."friend_id" = '127'))
LIMIT 1;
When I do a call on that function in my cluster.js
var conversation = query.checkRoom(data.userId,data.friendId));
I get that conversation is undefined.
Found a couple of solutions to catch the object Promise but didnt worked.
Looking forward to your answers.
EDIT
Managed to do that but when calling the query I want to add that answer to a var so I can use it later on. If now i`m doing something like var
conversationId = query.checkRoom(data.userId, data.friendId).then(function(conversation) { return conversation.dataValues.id; })
I get that my var conversationId is [object Promise] .
How can I get and use that Promise outside .then() function ?
You're trying to use checkRoom() as a synchronous function, which it isn't (like most functions dealing with I/O in Node).
Instead, you should return the promise returned by findOne() and handle the resolving (and rejection) of that promise in your calling code:
// query.js
exports.checkRoom = function(user_id, friend_id) {
return models.Conversations.findOne({ where: { $or: [{user_id: user_id , friend_id: friend_id}, {user_id: friend_id , friend_id: user_id}] }} );
}
// calling code
query.checkRoom(data.userId, data.friendId).then(function(conversation) {
// do something with the database result
...
})

Building model "user" to "user_flag" to "user"

I have two models:
User
'use strict';
var Mystical = require('../mystical'),
Blog = require('./blog'),
Flag = require('./user/flag');
module.exports = Mystical.db.Model.extend({
tableName: 'user',
defaults: {
isAdmin: 0,
isConfirmed: 0
},
blogs: function blogs() {
return this.hasMany(Blog, 'userId');
},
flags: function flags() {
return this.hasMany(Flag, 'userId');
}
});
...and Flag:
'use strict';
var Mystical = require('../../mystical'),
User = require('../../model/user');
module.exports = Mystical.db.Model.extend({
tableName: 'user_flag',
user: function user() {
return this.belongsTo(User, 'id');
}
});
On a page I try to get a flag:
(new Flag({ content: req.params.identifier, flag: 'register_complete' })).fetch({ withRelated: ['user'] }).then(onData).otherwise(function(error) {
console.log(error);
});
But every time I call this function the otherwise callback gets triggered:
TypeError: object is not a function
at exports.Relation.RelationBase.extend.relatedInstance (/my/secret/dir/node_modules/bookshelf/dialects/sql/relation.js:217:29)
at exports.Relation.RelationBase.extend.init (/my/secret/dir/node_modules/bookshelf/dialects/sql/relation.js:43:39)
at exports.Model.ModelBase.extend.belongsTo (/my/secret/dir/node_modules/bookshelf/dialects/sql/model.js:37:76)
at user (/my/secret/dir/core/model/user/flag.js:10:21)
at EagerBase.fetch (/my/secret/dir/node_modules/bookshelf/dialects/base/eager.js:56:40)
at /my/secret/dir/node_modules/bookshelf/dialects/sql/model.js:232:60
at NearFulfilledProxy.when (/my/secret/dir/node_modules/bookshelf/node_modules/when/when.js:465:43)
at Object._message (/my/secret/dir/node_modules/bookshelf/node_modules/when/when.js:389:25)
at deliver (/my/secret/dir/node_modules/bookshelf/node_modules/when/when.js:299:7)
at /my/secret/dir/node_modules/bookshelf/node_modules/when/when.js:296:63
For now I have tried to generate my association with hasMany, hasOne, belongsTo as well as belongsToMany. But nothing works...
Anyone an idea?
I have a feeling this might be a circular reference problem:
Try replacing:
return this.hasMany(Flag, 'userId');
with
return this.hasMany(require('./user/flag'), 'userId');
and
return this.belongsTo(User, 'id');
with
return this.belongsTo(require('../../model/user'), 'id');
and see if you still have the same issue.

Resources