If I leave mocha watching for changes, every time I save a file mongoose throws the following error:
OverwriteModelError: Cannot overwrite Client model once compiled
I know that mongoose won't allow to define a model twice, but I don't know how to make it work with mocha --watch.
// client.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var clientSchema = new Schema({
secret: { type: String, required: true, unique: true },
name: String,
description: String,
grant_types: [String],
created_at: { type: Date, default: Date.now }
});
module.exports = mongoose.model('Client', clientSchema);
And here is the test
// client-test.js
var chai = require('chai');
var chaiHttp = require('chai-http');
var mongoose = require('mongoose');
var server = require('../../app');
var Client = require('../../auth/models').Client;
var should = chai.should();
chai.use(chaiHttp);
describe('client endpoints', function() {
after(function(done) {
mongoose.connection.close();
done();
});
it('should get a single client on /auth/client/{clientId} GET', function(done) {
var clt = new Client({
name: 'my app name',
description: 'super usefull and nice app',
grant_types: ['password', 'refresh_token']
});
clt.save(function(err) {
chai.request(server)
.get('/auth/client/' + clt._id.toString())
.end(function(err, res) {
res.should.have.status(200);
res.should.be.json;
res.body.should.have.property('client_id');
res.body.should.not.have.property('secret');
res.body.should.have.property('name');
res.body.should.have.property('description');
done();
});
});
});
});
I had the same issue. My solution was to check whether the model was created/compiled yet, and if not then do so, otherwise just retrieve the model.
using mongoose.modelNames() you can get an array of the names of your models. Then use .indexOf to check if the model you want to get is in the array or not. If it is not, then compile the model, for example: mongoose.model("User", UserSchema), but if it is already defined (as is the case with mocha --watch), simply retrieve the model (don't compile it again), which you can do with for example: mongoose.connection.model("User").
This is a function which returns a function to do this checking logic, which itself returns the model (either by compiling it or just retrieving it).
const mongoose = require("mongoose");
//returns a function which returns either a compiled model, or a precompiled model
//s is a String for the model name e.g. "User", and model is the mongoose Schema
function getModel(s, model) {
return function() {
return mongoose.modelNames().indexOf(s) === -1
? mongoose.model(s, model)
: mongoose.connection.model(s);
};
}
module.exports = getModel;
This means you have to require your model a bit differently, since you are likely replacing something like this:
module.exports = mongoose.model("User", UserSchema);
which returns the model itself,
with this:
module.exports = getModel("User", UserSchema);
which returns a function to return the model, either by compiling it or just retrieving it. This means when you require the 'User' model, you would want to call the function returned by getModel:
const UserModel = require("./models/UserModel")();
I hope this helps.
Here is a simpler code for the function getModel() that George is proposing
function getModel(modelName, modelSchema) {
return mongoose.models[modelName] // Check if the model exists
? mongoose.model(modelName) // If true, only retrieve it
: mongoose.model(modelName, modelSchema) // If false, define it
}
For a larger explanation on how to define and require the model, look here
Hope this helps :)
This worked for me,
place on the top of your test file:
const mongoose = require("mongoose");
mongoose.models = {};
mongoose.modelSchemas = {};
Related
I was trying to use mongoose schema virtual functions to implement flags, But I could not make them work The mongoose database is hosted on mongodb altas. I have tried deleting the whole collection and staring again.
Here is a simplified example:
Let us say I have a basic User Schema:
const mongoose = require('mongoose')
const UserSchema = new mongoose.Schema({
name: String,
email: String
}, {toObject: {virtuals: true, getters: true}})
UserSchema.virtual('status').get(() => {
return this.name
})
const User = mongoose.model('User', UserSchema)
module.exports = {
User,
UserSchema
}
In app.js I have the following:
const mongoose = require("mongoose");
const express = require("express");
const {User} = require("./models/User")
const app = express();
app.use(express.urlencoded({extended: true}));
app.use(express.json());
mongoose.connect(process.env.DB_URL, {
useNewUrlParser: true,
useUnifiedTopology: true
});
User.findById("600ae8001931ad49eae40c03", (err, doc) => {
console.log(doc.status)
})
const port = process.env.PORT || 5000;
app.listen(port, () => {
console.log(`server at http://localhost:${port}`);
});
I made sure the id exists, but the result is always undefined. What am I doing wrong here?
based on mongoose documentation
Do not declare methods using ES6 arrow functions (=>). Arrow functions
explicitly prevent binding this, so your method will not have access
to the document and the above examples will not work
so you can not use arrow function in get, do like this :
UserSchema.virtual('status').get(function() {
return this.name
})
It's because of how you are declaring your function being passed to get. The way you are doing it is changing the this reference.
UserSchema.virtual('status').get(function() {
return this.name
})
The above would maintain it
To future users peaple that looking for the answer, beside this answer - if it's not solve the problem and still get undefined -
the problem maybe because you didn't selec the attribute that used in the virtual middleware
Our Schema
const monthSchema = new mongoose.Schema(
{
name: String,
numOfDays: Number,
},
{ toJSON: { virtuals: true }, toObject: { virtuals: true } }
);
if you use numOfDays in virtual middleware to calculate the weeks like this:
MonthSchema.virtual('NumOfWeeks').get(function() {
return this.numOfDays / 7
})
this you must be sure that numOfDays are selected
const user = await User.find().select('name'); // this.numOfDays will be undifined
const user = await User.find().select('-numOfDays'); // this.numOfDays will be undifined
const user = await User.find().select('name numOfDays'); // this.numOfDays will be not undifined
I spent whole day to figure this, may be this will help someone
Hi I am learning to use mongoose middleware.
What I want is:
When I create a user, it automatically create a userPreference document for that user.
That's why I define a 'save' middleware.
Here is my code:
const mongoose = require('mongoose')
var Schema = mongoose.Schema;
//I define the schema here
const userInfo = new Schema({
Id:{ type: String,unique:true },
key:{ type: String, unique: true },
})
const userPreference = new Schema({
Id:{ type: String,unique:true },
date:Date,
color:String
})
//This is where I define the Model UserPreference , but my code says I didn't define it?
const UserPreference = mongoose.model('userPreference',userPreference);
//Here I add the middleware
userInfo.post('save', async function() {
console.log('Middleware begins')
await this.model("UserPreference").create({Id:this.Id})
});
const UserInfo = mongoose.model('userInfo',userInfo);
//Here I connectToServer and try to create the user
connectToServer( function( err, res ) {
if (err) {console.log(err);}
createUser();
})
async function createUser(){
var Id = '12345';
await UserInfo.create({Id:Id,key:'My-key'});
}
When I run my code, the console outputs the Middleware begins (so the middleware fires successfully).UserInfo also creates successfully.
However then userPreference fails to create and I always get this error:
MissingSchemaError: Schema hasn't been registered for model >"UserPreference".Use mongoose.model(name, schema)
I double check I define UserPreference in my code, and it's also before I use that UserPreference model.
const UserPreference = mongoose.model('userPreference',userPreference);
Because you registered your model with userPreference in:
const UserPreference = mongoose.model('userPreference',userPreference)
So you need to call it in this way:
this.model("userPreference").create({Id:this.Id})
Or:
UserPreference.create({Id:this.Id})
I'm testing a mongoose model's validation, while trying to mock a validator, the model still has the reference to the original function, so the validation keeps calling the original function.
I want to test that the validator function get's called, however since the validator goes to the db I need to mock it.
This is my model:
const { hasRequiredCustoms } = require('../utils/validators')
const ProductSchema = new Schema({
customs: {
type: [String],
validate: hasRequiredCustoms // <- This is the validator
}
})
const Product = mongoose.model('Product', ProductSchema)
module.exports = Product
The original validators:
module.exports = {
hasRequiredCustoms(val) {
console.log('Original')
// validation logic
return result
},
//etc...
}
This is the mock for validators:
const validators = jest.genMockFromModule('../validators')
function hasRequiredCustoms (val) {
console.log('Mock')
return true
}
validators.hasRequiredCustoms = hasRequiredCustoms
module.exports = validators
And the test:
test('Should be invalid if required customs missing: price', done => {
jest.mock('../../utils/validators')
function callback(err) {
if (!err) done()
}
const m = new Product( validProduct )
m.validate(callback)
})
Every time I run the tests the console logs the Original. Why is the reference still going back to the original module? seems like I'm missing some super essential concept of how require works or the way mongoose stores the validators references.
Thanks for the help.
I am quite new to testing nodejs. So my approach might be completely wrong. I try to test a mongoose models pre-save-hook without hitting the Database. Here is my model:
// models/user.js
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
UserSchema = new Schema({
email: {type: String, required: true},
password: {type: String, required: true}
});
UserSchema.pre('save', function (next) {
const user = this;
user.email = user.email.toLowerCase();
// for testing purposes
console.log("Pre save hook called");
next();
});
module.exports = mongoose.model("User", UserSchema);
As I said, I do not want to hit the Database with my test, so I tried using a sinon stub of the Users save() method:
// test/models/user.js
const sinon = require("sinon");
const chai = require("chai");
const assert = chai.assert;
const User = require("../../models/user");
describe("User", function(){
it("should convert email to lower case before saving", (done) => {
const user = new User({email: "Valid#Email.com", password: "password123"});
const saveStub = sinon.stub(user, 'save').callsFake(function(cb){ cb(null,this) })
user.save((err,res) => {
if (err) return done(err);
assert.equal(res.email,"valid#email.com");
done();
})
})
});
However, If I do it like that the pre-save hook will not be called. Am I on the wrong path or am I missing something? Or is there maybe another way of triggering the pre-save hook and testing its outcome? Thanks very much in advance!
Before we start: I'm looking for the same thing as you do and I've yet to find a way to test the different hooks in Mongoose without a database. It's important that we distinguish between testing our code and testing mongoose.
Validation is middleware. Mongoose registers validation as a pre('save') hook on every schema by default. http://mongoosejs.com/docs/validation.html
Considering that validate will always be added to the model and I wish to test the automated fields in my model, I've switched from save to validate.
UserSchema = new Schema({
email: {type: String, required: true},
password: {type: String, required: true}
});
UserSchema.pre('validate', function(next) {
const user = this;
user.email = user.email.toLowerCase();
// for testing purposes
console.log("Pre validate hook called");
next();
});
The test will now look like:
it("should convert email to lower case before saving", (done) => {
const user = new User({email: "VALID#EMAIL.COM", password: "password123"});
assert.equal(res.email,"valid#email.com");
}
So What About the Pre Save Hook?
Because I've moved the business logic for automatic fields from 'save' to 'validate', I'll use 'save' for database specific operations. Logging, adding objects to other documents, and so on. And testing this only makes sense with integration with a database.
I just faced the same issue and managed to solve it by extracting the logic out of the hook, making it possible to test it in isolation. With isolation I mean without testing anything Mongoose related.
You can do so by creating a function, that enforces your logic, with the following structure:
function preSaveFunc(next, obj) {
// your logic
next();
}
You can then call it in your hook:
mySchema.pre('save', function (next) { preSaveFunc(next, this); });
This will make the reference to this available inside the function, so you can work with it.
The extracted part can then be unit tested by overwriting the next function to a function without a body.
Hope this will help anyone as it actually was a pain to solve this with my limited knowledge on Mongoose.
I'm new to mongoose and I'm having a hard time finding the issue within my code. I'm building a REST server using Sails.js and Mongoose. I have a node module (e.g. "sails-mongoose") for exporting mongoose, where I also connect to my database:
var mongoose = require('mongoose');
mongoose.connect('mongodb://#localhost:27017/fooria');
module.exports = mongoose;
And in my model.js:
var adapter = require('sails-mongoose');
var schema = new adapter.Schema({
firstname: {
type: String,
required: true,
trim: true
}
});
module.exports = {
schema: schema,
model: adapter.model('Collection', schema)
}
In my controller's create method I have:
create: function(req, res, next) {
var userData = {firstname: 'Test'};
var users = new Users.model(userData);
users.save(function(err, data){
if (err) return res.json(err, 400);
res.json(data, 201);
});
}
When running create method, the entry is saved to the Mongodb collection but the callback is never reached. Can someone please help me on this track, as I found similar questions but none helped me though. Thanks!
I suppose your are using Express. According Express docs you are calling res.json using incorrect parameters (wrong order).
Correct format:
res.json(code, data)
Example:
res.json(500, { error: 'message' })