I am trying to stub the mongoose dependency used in this object:
var Page = function(db) {
var mongoose = db || require('mongoose');
if(!this instanceof Page) {
return new Page(db);
}
function save(params) {
var PageSchema = mongoose.model('Page');
var pageModel = new PageSchema({
...
});
pageModel.save();
}
Page.prototype.save = save;
}
module.exports = Page;
Using the answer for this question, I've tried doing this:
mongoose = require 'mongoose'
sinon.stub mongoose.Model, 'save'
But I got the error:
TypeError: Attempted to wrap undefined property save as function
I also tried this:
sinon.stub PageSchema.prototype, 'save'
And then I got the error:
TypeError: Should wrap property of object
Can anyone help with this? What am I doing wrong?
I've analysed mongoose source and don't think this is possible. Save function is not defined on model, but dynamically generated by hooks npm which enables pre/post middleware functionality.
However, you can stub save on instance like this:
page = new Page();
sinon.stub(page, 'save', function(cb){ cb(null) })
UPDATE: Stubbing out pageModel
First, you need to make pageModel accessible by setting it as own property of Page (this.pageModel = xxx). Then, you can stub it like shown bellow:
mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');
mongoose.set('debug', true);
schema = new mongoose.Schema({title: String});
mongoose.model('Page', schema);
var Page = function(db) {
var mongoose = db || require('mongoose');
if(!this instanceof Page) {
return new Page(db);
}
var PageSchema = mongoose.model('Page');
this.pageModel = new PageSchema();
function save(params, cb) {
console.log("page.save");
this.pageModel.set(params);
this.pageModel.save(function (err, product) {
console.log("pageModel.save");
cb(err, product);
});
}
Page.prototype.save = save;
};
page = new Page();
sinon = require('sinon');
sinon.stub(page.pageModel, 'save', function(cb){
cb("fake error", null);
});
page.save({ title: 'awesome' }, function (err, product) {
if(err) return console.log("ERROR:", err);
console.log("DONE");
});
I recommend you to use mock instead of stub, that will check the method really exists on the original object.
var page = new Page();
// If you are using callbacks, use yields so your callback will be called
sinon.mock(page)
.expects('save')
.yields(someError, someResult);
// If you are using Promises, use 'resolves' (using sinon-as-promised npm)
sinon.mock(page)
.expects('save')
.resolves(someResult);
Take a look to sinon-mongoose. You can expects chained methods (on both, Mongoose Models and Documents) with just a few lines (there are working examples on the repo).
page = new Page();
sinon.stub(page, 'save', function(cb){ cb(null) })
Above code is deprecated.
Please try to add fake function for your stub as below -
sinon.stub(page, 'save').callsFake(function(cb){
// do your Fake code
cb(null)
})
Related
I am .NET developer and learning MEAN stack, developing an API for simple post and comment app. I am using MVC pattern without view as it is API. Here is how I am trying to create my model function :
const User = module.exports = mongoose.model('User',UserSchema);
module.exports.getUsers = function(callback){
User.find()
.exec(function(err,list_users){
if(err) { callback(); }
return list_users;
});
}
And here is the controller :
exports.user_list = function(req,res,next){
User.getUsers((err,list_users) => {
if(err){ next(err);}
res.json({user_list : list_users});
})
}
I am getting an error as getUsers is not getting imported. I am trying here to separate the data access code from controller and make controller only to manage request and response as I have been doing in .NET. Any help how can I achieve this?
I would look through the documentation to get familiar with how to use mongoose.
First thing I see is that you are not defining the methods correctly according to the documentation here http://mongoosejs.com/docs/guide.html#methods.
But you do not have to define a find() function. Mongoose already has it built in.
So the next step would be how are you creating the instance of the Model in your controller.
Your model should be:
const mongoose = require('mongoose'),
UserSchema = mongoose.Schema();
const User = new UserSchema({
// define user
});
module.exports = mongoose.model('User',UserSchema);
And the controller:
const User = require('path_to_User_model');
exports.user_list = function(req,res,next){
User.find((err,result)=>{
if(err)
// log err and send response
else
// send the result ex: res.json(result);
});
}
also read through the setup on the npmjs website. https://www.npmjs.com/package/mongoose
So to create have your data functions and have your controller use that function you may try
const mongoose = require('mongoose'),
UserSchema = mongoose.Schema();
const User = new UserSchema({
// define user
});
// data functions
User.methods.getUsers = function (){
User.find((err,result)=>{
if(err)
// log err and return something
return false;
else
return result;
});
}
module.exports = mongoose.model('User',UserSchema);
then the controller
const User = require('path_to_User_model');
exports.user_list = function(req,res,next){
var users = User.getUsers();
if (!users)
res.status(500).send('error_page');
else
res.json(users);
}
I’m trying to call a generator function inside a class from the constructor, it runs but nothing happens (my console.log are not printing) as if the generator function is never called.
Update #1:
Here's an updated version of my code. I am able to access my findOne function with the next() function, but since I wrapped "users" with co-monk, I tough there was no need to call it. I'm still not sure why I need to call next(0) function 2 times to jump over the yield call.
Therefore, I'm now getting undefined when I print the output of "userData".
If my issue is related to understand on how yield works, maybe pointing me to direction could help me. I tried using generator functions with yield calls apart from a class and it worked perfectly fine with monk/co-monk.
Update #2:
I should also mention that I'm using babel6 for transcript.
"use strict";
var monk = require("monk");
var wrap = require("co-monk");
var db = monk("localhost/test");
var users = wrap(db.get("users"));
class User {
constructor(user) {
if (typeof user == "object") {
var findUser = this.findOne(user.id);
findUser.next();
findUser.next();
if(findUser != null){
this._user = user;
}
}
else {
console.error("user parameter is not an oject");
return false;
}
}
*findOne(id) {
var userData = yield users.findOne({_id: id});
console.log(userData); // Getting undefined
this._user = userData;
};
}
var _user = new User({id : "1234"});
console.log(_user);
export default User;
It looks like the docs for co-monk are a bit vague, but looking at the one test it has, it needs to be used within co.
var co = require("co");
// rest of your code
findOne(id) {
return co(function* () {
var userData = yield users.findOne({_id: id});
console.log(userData); // Getting undefined
this._user = userData;
});
};
What it won't allow you to do is seamlessly transition async code back into sync code, so this.findOne() will return a promise. As a result, your User object may not be populated immediately after called new User({id : "1234"}).
tried with the lib co and it worked.
It looks like co-monk is broken inside a generator function inside a class at this moment.
so here's what worked for me :
"use strict";
var monk = require("monk");
//var wrap = require("co-monk");
var db = monk("localhost/test");
var users = (db.get("users"));
var co = require('co');
class User {
constructor(user) {
if (typeof user == "object") {
var findUser = this.findOne(user.id);
this._user = user;
}
else {
console.error("The user params is not an object");
}
}
findOne(id) {
co(function* () {
return yield users.findOne({_id: id});
}).then(function (val) {
console.log(val);
}, function (err) {
console.error(err.stack);
});
};
}
I'm building a basic blog in Node.js / Express using MongoDB w/ Mongoose ORM.
I have a pre 'save' hook that I'd like to use to auto-generate a blog/idea slug for me. This works fine and well, except for the part where I want to query to see if there are any other existing posts with the same slug before continuing.
However, it appears that this does not have access to .find or .findOne() and so I keep getting an error.
What's the best way to approach this?
IdeaSchema.pre('save', function(next) {
var idea = this;
function generate_slug(text) {
return text.toLowerCase().replace(/[^\w ]+/g,'').replace(/ +/g,'-').trim();
};
idea.slug = generate_slug(idea.title);
// this has no method 'find'
this.findOne({slug: idea.slug}, function(err, doc) {
console.log(err);
console.log(doc);
});
//console.log(idea);
next();
});
Unfortunately, it's not documented very well (no mention of it in the Document.js API docs), but Documents have access to their models through the constructor field - I use it all the time for logging things from plugins, which gives me access to which model they're attached to.
module.exports = function readonly(schema, options) {
schema.pre('save', function(next) {
console.log(this.constructor.modelName + " is running the pre-save hook.");
// some other code here ...
next();
});
});
For your situation, you should be able to do:
IdeaSchema.pre('save', function(next) {
var idea = this;
function generate_slug(text) {
return text.toLowerCase().replace(/[^\w ]+/g,'').replace(/ +/g,'-').trim();
};
idea.slug = generate_slug(idea.title);
// this now works
this.constructor.findOne({slug: idea.slug}, function(err, doc) {
console.log(err);
console.log(doc);
next(err, doc);
});
//console.log(idea);
});
In this you have got the document, not the model. Method findOne is not present on the document.
If you need the model, you can always retrieve it as is shown here. But more clever would be to just assign the model to a variable at the point of creation.
Then use this variable anywhere you desire. If it is in another file, then use module.exports and require to get it anywhere else in your project.
Something like this:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/dbname', function (err) {
// if we failed to connect, abort
if (err) throw err;
var IdeaSchema = Schema({
...
});
var IdeaModel = mongoose.model('Idea', IdeaSchema);
IdeaSchema.pre('save', function(next) {
var idea = this;
function generate_slug(text) {
return text.toLowerCase().replace(/[^\w ]+/g,'').replace(/ +/g,'-').trim();
};
idea.slug = generate_slug(idea.title);
// this has no method 'find'
IdeaModel.findOne({slug: idea.slug}, function(err, doc) {
console.log(err);
console.log(doc);
});
//console.log(idea);
next();
});
// we connected ok
})
I am new to Mocha, and only a little experience with Node/Express. My DbProvider module works perfectly (mongodb) when I am access it through my Express app. And now I want to test it. I have read the Mocha site and some tutorials I could find. But I have big trouble of finding an real-world example out there that I could follow (any links much appreciated!).
Here is my unsuccessful attempt to write a testfile:
var DbProvider = require('../db').DbProvider;
var assert = require('assert');
var dbProvider = new DbProvider('localhost', 27017, 'mydb');
var util = require('util');
console.log(util.inspect(dbProvider));
describe('DbProvider', function(){
describe('findAllNotes', function(){
it('should return some notes', function(){
dbProvider.findAllNotes({}, function (err, result){
assert(result.length > 0);
});
})
})
})
The output I get is this:
$ mocha
{}
✖ 1 of 1 test failed:
1) DbProvider findAllNotes should return some notes:
TypeError: Cannot call method 'collection' of undefined
at DbProvider.doOperation (/Users/frode/Node/json/db.js:46:11)
at DbProvider.findAllNotes (/Users/frode/Node/json/db.js:56:8)
at Context.<anonymous> (/Users/frode/Node/json/test/test.js:15:18)
(cutting out the rest)
It seems that I am unsuccessful to create the dbProvider. This works perfectly in my app... How can I make this work? (And perhaps also: Is the way I have set it up in general ok?)
Edit: Here is the db.js file:
// Database related
'use strict';
var MongoClient = require('mongodb').MongoClient;
var BSON = require('mongodb').BSONPure;
var ObjectID = require('mongodb').ObjectID;
var checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$");
var Validator = require('validator').Validator
var fieldMaxLength = 1024;
//var util = require('util');
var DbProvider = function(host, port, database) {
var dbUrl = "mongodb://"+host+":"+port+"/"+database;
var self = this;
MongoClient.connect(dbUrl, function(err, db) {
self.db = db;
});
};
// Do some basic validation on the data we get from the client/user
var validateParams = function(params, callback) {
// Let´ do a quick general sanity check on the length on all fields
for(var key in params) {
if(params[key].length > fieldMaxLength) callback(new Error('Field ' + key + ' is too long.'));
}
// and the let us check some specific fields better
if (params._id) {
if(checkForHexRegExp.test(params._id)) {
// In case of '_id' we also need to convert it to BSON so that mongodb can use it.
params._id = new BSON.ObjectID(params._id);
} else {
var err = {error: 'Wrong ID format'};
}
}
if(err) callback(err);
}
// Generalized function to operations on the database
// Todo: Generalize even more when user authenication is implemented
DbProvider.prototype.doOperation = function(collection, operation, params, callback) {
validateParams(params, callback);
var operationCallback = function(err, result) {
callback(err, result);
};
this.db.collection(collection, function(err, collection) {
if(operation==='find') {
collection.find().toArray(operationCallback);
} else {
collection[operation](params, operationCallback);
}
});
}
DbProvider.prototype.findAllNotes = function(params, callback) {
this.doOperation('notes', 'find', params, callback);
};
DbProvider.prototype.findNoteById = function(params, callback) {
this.doOperation('notes', 'findOne', params, callback);
};
DbProvider.prototype.saveNote = function(params, callback) {
params.created_at = new Date();
this.doOperation('notes', 'save', params, callback);
};
DbProvider.prototype.deleteNote = function(params, callback) {
this.doOperation('notes', 'remove', params, callback);
};
DbProvider.prototype.findUser = function(params, callback) {
this.doOperation('users', 'findOne', params, callback);
};
exports.DbProvider = DbProvider;
SOLUTION:
After Benjamin told me to handle the async nature of mongodb connecting to the database, and inspired by his suggestion on how to adapt the code, I split the constructor function DbProvider into two parts. The first part, the constructor DbProvider now just saves the db-parameters into a variable. The second part, a new function, DbProvider.connect does the actual async connection. See below.
var DbProvider = function(host, port, database) {
this.dbUrl = "mongodb://"+host+":"+port+"/"+database;
};
DbProvider.prototype.connect = function(callback) {
var self = this;
MongoClient.connect(this.dbUrl, function(err, db) {
self.db = db;
callback();
});
};
So I can now make a Mocha test like this (and async tests also need the "Done" included, like you see in the code below):
var assert = require('assert');
var DbProvider = require('../db').DbProvider;
var dbProvider = new DbProvider('localhost', 27017, 'nki');
describe('DbProvider', function(){
describe('findAllNotes', function(){
it('should return some notes', function(done){
dbProvider.connect(function(){
dbProvider.findAllNotes({}, function (err, result){
assert(result.length > 0);
done();
});
});
})
})
})
Note that the acutal test ("should return some notes") is nothing to be proud of. What I wanted here was to get set up so I am able to test something. Now that I finally acutally can do that, I need to write good tests (something in the the line of having a test database, clear it, test insert a document, test search for a document, and so on...).
And in my Express app, I used to set up the database like this:
var DbProvider = require('./db').DbProvider;
// Setup db instance
var dbProvider = new DbProvider(
process.env.mongo_host || 'localhost',
process.env.mongo_port || 27017,
process.env.mongo_db || 'nki'
);
Now I do the same, but in addition, I call the new connect-function:
// Connect to db. I use (for now) 1 connection for the lifetime of this app.
// And I do not use a callback when connecting here (we do in the testing)
dbProvider.connect(function(){});
Benjamin actually pointed out that it may be ok but not the best practice to have the database set up like this in an Express app. But until I figure out what the best practice really is, I will leave this code as it is. Here is a couple of links reagarding the subject I found (but I have still not concluded of how I will solve it myself):
What's the best practice for MongoDB connections on Node.js? and
[node-mongodb-native] MongoDB Best practices for beginner
If you like, you are very welcome to follow/fork/whatever this project on github. My goal is to get it as production ready I can. The link is
https://github.com/frodefi/node-mongodb-json-server
MongoClient.connect is asynchronous.
From the docs:
callback (function) – this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the initialized db object or null if an error occured.
That means DbProvider.db isn't set yet in the test which is why you're getting undefined.
In here:
MongoClient.connect(dbUrl, function(err, db) {
self.db = db;
});
You're telling it "update self.db after the connection happened", which is at least one event loop tick after this one (but may be more). In your mocha code you're executing your .describe and .it methods right after creating your DbProvider instance which means it was not initialized yet.
I suggest that you re-factor DbProvider to return a callback instead of being a constructor function. Maybe something along the lines of:
var getDbProvider = function(host, port, database,callback) {
var dbUrl = "mongodb://"+host+":"+port+"/"+database;
MongoClient.connect(dbUrl, function(err, db) {
self.db = db;
callback(db);
});
};
Which also means moving all the DBProvider methods to an object (maybe the callback will return a dbprovider object and not just a db?).
Another bug solved by using Unit Tests :)
This is what I used: https://github.com/arunoda/mocha-mongo
It has set of testing helpers for mongodb
var n = new Chat();
n.name = "chat room";
n.save(function(){
//console.log(THE OBJECT ID that I just saved);
});
I want to console.log the object id of the object I just saved. How do I do that in Mongoose?
This just worked for me:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/lol', function(err) {
if (err) { console.log(err) }
});
var ChatSchema = new Schema({
name: String
});
mongoose.model('Chat', ChatSchema);
var Chat = mongoose.model('Chat');
var n = new Chat();
n.name = "chat room";
n.save(function(err,room) {
console.log(room.id);
});
$ node test.js
4e3444818cde747f02000001
$
I'm on mongoose 1.7.2 and this works just fine, just ran it again to be sure.
Mongo sends the complete document as a callbackobject so you can simply get it from there only.
for example
n.save(function(err,room){
var newRoomId = room._id;
});
You can manually generate the _id then you don't have to worry about pulling it back out later.
var mongoose = require('mongoose');
var myId = mongoose.Types.ObjectId();
// then set it manually when you create your object
_id: myId
// then use the variable wherever
You can get the object id in Mongoose right after creating a new object instance without having to save it to the database.
I'm using this code work in mongoose 4. You can try it in other versions.
var n = new Chat();
var _id = n._id;
or
n.save((function (_id) {
return function () {
console.log(_id);
// your save callback code in here
};
})(n._id));
Other answers have mentioned adding a callback, I prefer to use .then()
n.name = "chat room";
n.save()
.then(chatRoom => console.log(chatRoom._id));
example from the docs:.
var gnr = new Band({
name: "Guns N' Roses",
members: ['Axl', 'Slash']
});
var promise = gnr.save();
assert.ok(promise instanceof Promise);
promise.then(function (doc) {
assert.equal(doc.name, "Guns N' Roses");
});
Well, I have this:
TryThisSchema.post("save", function(next) {
console.log(this._id);
});
Notice the "post" in the first line. With my version of Mongoose, I have no trouble getting the _id value after the data is saved.
With save all you just need to do is:
n.save((err, room) => {
if (err) return `Error occurred while saving ${err}`;
const { _id } = room;
console.log(`New room id: ${_id}`);
return room;
});
Just in case someone is wondering how to get the same result using create:
const array = [{ type: 'jelly bean' }, { type: 'snickers' }];
Candy.create(array, (err, candies) => {
if (err) // ...
const [jellybean, snickers] = candies;
const jellybeadId = jellybean._id;
const snickersId = snickers._id;
// ...
});
Check out the official doc
Actually the ID should already be there when instantiating the object
var n = new Chat();
console.log(n._id) // => 4e7819d26f29f407b0... -> ID is already allocated
Check this answer here: https://stackoverflow.com/a/7480248/318380
As per Mongoose v5.x documentation:
The save() method returns a promise. If save() succeeds, the
promise resolves to the document that was saved.
Using that, something like this will also work:
let id;
n.save().then(savedDoc => {
id = savedDoc.id;
});
using async function
router.post('/create-new-chat', async (req, res) => {
const chat = new Chat({ name : 'chat room' });
try {
await chat.save();
console.log(chat._id);
}catch (e) {
console.log(e)
}
});