Mongoose doesn't drop database and close connection properly in Mocha - javascript

I have two simple test but one of the test passed but not the other one because of the Schema getting compiled again.
OverwriteModelError: Cannot overwrite CheckStaging model once
compiled.
Here's my one test that passed because it's being run first.
var mongoose = require('mongoose'),
StagingManager = require('../lib/staging_manager'),
expect = require('expect.js');
describe('Staging manager', function() {
var StagingModel;
beforeEach(function(done) {
mongoose.connect('mongodb://localhost/BTest');
StagingModel = new StagingManager(mongoose).getStaging();
done();
});
describe('find one', function() {
it('should insert to database', function(done) {
// Do some test which works fine
});
});
afterEach(function (done) {
mongoose.connection.db.dropDatabase(function () {
mongoose.connection.close(function () {
done();
});
});
});
});
And here's the test that failed
var StagingUtil = require('../lib/staging_util'),
StagingManager = require('../lib/staging_manager'),
mongoose = require('mongoose');
describe('Staging Util', function() {
var stagingUtil, StagingModel;
beforeEach(function(done) {
mongoose.connect('mongodb://localhost/DBTest');
StagingModel = new StagingManager(mongoose).getStaging();
stagingUtil = new StagingUtil(StagingModel);
done();
});
describe('message contains staging', function() {
it('should replace old user with new user', function(done) {
// Do some testing
});
});
afterEach(function (done) {
mongoose.connection.db.dropDatabase(function () {
mongoose.connection.close(function () {
done();
});
});
});
});
And here's my staging manager
var Staging = function(mongoose) {
this.mongoose = mongoose;
};
Staging.prototype.getStaging = function() {
return this.mongoose.model('CheckStaging', {
user: String,
createdAt: { type: Date, default: Date.now }
});
};
module.exports = Staging;

mongoose.model registers a model with Mongoose, so you should only be calling that once rather than each time you call getStaging. Try something like this for your staging model instead:
var mongoose = require('mongoose');
var StagingModel = new mongoose.Schema({
user: String,
createdAt: { type: Date, default: Date.now }
});
mongoose.model('CheckStaging', StagingModel);
Then in your consuming code, use
var mongoose = require('mongoose');
require('../lib/staging_manager');
var StagingModel = mongoose.model('CheckStaging');
The require will only execute once, so the model should only be registered with mongoose once.
As an aside, for unit testing, mockgoose is an excellent mocking library to mock out mongoose - worth investigating!

Related

How do I mock Mongoose calls without mocking the callback logic in exec?

I've written a test against my itemService function getItemsForUser to assert on the Items returned from a Mongoose find call. However, my test is currently passing regardless of the user object passed in because it mocks out the whole callback passed to exec.
My question is, how can I mock out only the Items returned by the find and populate but not skip the rest of the logic, so that the test does actually call doUsersContainUser?
itemService.js
const Item = require('../models/item');
exports.getItemsForUser= function(user, cb)
{
Item.find({}).populate('users').exec(function (err, itemList)
{
if (err) { throw next(err) }
else {
cb(null, itemList.filter(i=> doUsersContainUser(i, user._id));
}
});
};
function doUsersContainUser(item, userId) {
return item.users.some(u => hasValue(u, 'user', userId))
}
item.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ItemSchema = new Schema({
users: [{ type: Schema.Types.ObjectId, ref: 'User' }]
});
module.exports = mongoose.model('Item', ItemSchema );
itemServiceTest.js
it('should return all items for user', function () {
const allItems = itemsData.allItemsPopulated();
const user = { id: 10001 };
sinon.mock(Item).expects('find')
.chain('populate', 'users')
.chain('exec')
.returns(null, allItems);
itemService.getItemsForUser(user, function(err, result) {
assert.strictEqual(result, allItems);
});
});
I am using sinon-mongoose for the chained mongoose calls.

Winston logger setting the timestamp of the log to 12/31/1969 instead of current time

I'm setting up logging in an app with winston and occassionally when I run tests a separate file is created with the date 12/31/1969. Is there something explicit I need to put in the creation of the transport so that it knows what the current date is?
What's very interesting is this seems to be a system wide anomaly as the _log method, which doesn't use the new Date() syntax, but the moment.js library also results in a 12-31-1969 inside the log file:
My logger:
class Logger{
constructor(configs){
if (!configs) configs = {};
this.logDirectory = configs.directory ? path.join(__dirname, configs.directory) : path.join(__dirname, '../logs') ;
this.initialize();
this.date = moment().format("YYYY-MM-DD HH:mm:ss");
}
initialize() {
this._createTransportObj();
this._createLoggerObj();
}
_createTransportObj() {
const DailyRotateFile = winston.transports.DailyRotateFile;
this._transport = new DailyRotateFile({
filename: path.join(this.logDirectory, '/log-%DATE%.log'),
datePattern: 'YYYY-MM-DD',
level: 'info'
});
}
_createLoggerObj() {
this._logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [this._transport],
exitOnError: true
});
if (nodeEnv !== 'production') {
this._logger.add(new winston.transports.Console({
format: winston.format.simple()
}));
}
}
_log(type, msg, options) {
const logMsg = {};
const timestamp = moment().format("YYYY-MM-DD HH:mm:ss");
logMsg.level = type || 'info';
logMsg.time = timestamp;
logMsg.msg = msg || '';
logMsg.desc = options.description || '';
// get the user that made the request if available
if (options.user) logMsg.user = options.user;
// get the url endpoint that was hit if available
if (options.url) logMsg.url = options.url;
// if an error is sent through, get the stack
// and remove it from msg for readability
if (msg.stack) {
logMsg.stack = msg.stack;
msg = msg.message ? msg.message : msg;
}
// get the ip address of the caller if available
if (options.ip) logMsg.ip = options.ip;
// get the body of the request if available
if (options.body) logMsg.body = options.body;
// get the query string of the request if available
if (options.query) logMsg.query = options.query;
// get the params string of the request if available
if (options.params) logMsg.params = options.params;
const jsonString = JSON.stringify(logMsg);
this._logger.log(type, logMsg);
}
info(msg, options) {
return this._log('info', msg, options);
}
error(msg, options) {
return this._log('error', msg, options);
}
warn(msg, options) {
return this._log('warn', msg, options);
}
verbose(msg, options) {
return this._log('verbose', msg, options);
}
debug(msg, options) {
return this._log('debug', msg, options);
}
silly(msg, options) {
return this._log('silly', msg, options);
}
}
module.exports = { Logger };
I'm currently only testing it in a promise handler that my routes flow through:
const asyncHandler = fn => (req, res, next) => {
const logger = new Logger();
Promise.resolve(fn(req, res, next))
.then(result => {
if (req.body.password) delete req.body.password;
logger.info(result,
{ user: req.user.username,
url: req.originalUrl,
body: req.body,
description: '200:OK Response sent back successfully'
});
return res.status(200).json({ result })
})
.catch(e => {
console.log(e);
return res.status(400).json({ error: e.message })
});
};
module.exports = asyncHandler;
UPDATE*
ok, so it seems to not be the logger itself. I ran a batch of tests and noticed it's always the same route that triggers the date change. What's weird is I can't seem to figure out what's happening.
The route is:
and the app.use() statement is as follows:
finally the admin_access middleware is simple enought:
I've figured out if I break the endpoint in the app.js file before it hits admin_access the date is correct. However if I break in admin_access the date is 12-31-1969. So what could be happening between the two? Is there something I could be setting unintentionally on this route?
Figured it out. Turns out the package sinon-test was changing the system time.
I had my test setup like this:
const sinon = require('sinon');
const sinonTest = require('sinon-test');
const test = sinonTest(sinon);
describe('Test suite for route: /video/admin/create', ()=>{
let agent;
beforeEach(async function() {
// create temporary admin
admin.permissionId = 1;
await new NewUser().createUser(admin);
//login as admin
agent = chai.request.agent(server);
await agent
.post('/auth')
.send({ username: admin.username, password: admin.password });
});
afterEach(async function() {
// destroy temp admin
await new UserManager(admin).hardRemove();
});
it('should fail to create a video for not including video info', test(async function(){
const newVideo = { title: 'Test Video' };
const response = await agent.post('/video/admin/create')
.send(newVideo);
expect(response.status).to.equal(400);
}));
it('should create a video', test(async function(){
const newVideo = {
title: 'Test Video',
description: 'This is a description',
embed: 'https://youtu.be/SKbHjjZXdmc',
source: 'youtube',
type: 'lecture',
category: 'physics'
};
const response = await agent.post('/video/admin/create')
.send(newVideo);
// validations
expect(response.status).to.equal(200);
const { result } = response.body;
expect(result.title).to.equal(newVideo.title);
expect(result.description).to.equal(newVideo.description);
expect(result.embed).to.equal(newVideo.embed);
}));
});
When I unwrapped the test from the test() function everything worked correctly.

Unit Testing BelongsToMany relations in Sequelize using Jasmine

I'm building a simple blog style application for practice using Node and Sequelize (with the Yeoman angular-fullstack generator). I'm trying to write out some tests for my article model, but I'm unable to figure out the right way to structure my tests. The articles and users are connected by a two-way belongsToMany relationship.
I'm able to verify that the setter and getter methods exist for my objects, but am unable to verify that they work. The code I'm trying to get to work is the code commented out at the end of the file. I'd appreciate any help in getting that unit test to work correctly, thanks!
'use strict';
/* globals describe, expect, it, beforeEach, afterEach */
var app = require('../..');
import request from 'supertest';
import {User, Article} from '../../sqldb';
var user = [];
var article = [];
var genArticles = function(i) {
for(var i = 0; i < 3; i++) {
article[i] = genArticle(i);
}
return article;
}
var genUsers = function(i) {
for(var i = 0; i < 3; i++) {
user[i] = genUser(i);
}
return user;
}
var genUser = function(i) {
return User.build({
provider: 'local',
name: `Fake User ${i}`,
email: `test${i}#example.com`,
password: 'password'
});
};
var genArticle = function(i) {
return Article.build({
title: `Fake Article ${i}`,
body: `test${i}`
});
};
describe("Article-User relations", function() {
before(function() {
return User.sync().then(function() {
return User.destroy({ where: {} }).then(function() {
return Article.sync().then(function() {
return Article.destroy({ where: {} }).then(function() {
});
});
});
});
});
beforeEach(function(done) {
genArticles();
genUsers();
done();
});
afterEach(function() {
return User.sync().then(function() {
return User.destroy({ where: {} }).then(function() {
return Article.sync().then(function() {
return Article.destroy({ where: {} }).then(function() {
});
});
});
});
});
it('should begin with no data', function() {
expect(User.findAll()).to
.eventually.have.length(0);
return expect(Article.findAll()).to
.eventually.have.length(0);
});
describe("Check methods exist and", function(done) {
before(function(done) {
article.forEach((a) => {a.save()});
user.forEach((a) => {a.save()});
done();
});
it("should have setter methods", function() {
expect(typeof article[0].setUsers).to.equal("function");
expect(typeof user[0].setArticles).to.equal("function");
});
it("should have getter methods", function() {
expect(typeof article[0].getUsers).to.equal("function");
expect(typeof user[0].getArticles).to.equal("function");
});
/* THIS IS THE TEST THAT ISN'T WORKING */
/*
it("setter and getter methods for article objects should work", function() {
return article[0].setUsers(user).then(() => {
return article[0].hasUsers(user).then( result => {
expect(result).should.eventually.equal.true;
})
}).catch( e => { console.log(e) });
//expect(typeof user[0].setArticles).to.equal("function");
});
*/
});
});

Forcing Node.js modules to follow an interface

I have a simple generic DB interface and want to force my DB adapters to follow that interface. So, both db.js and fake_mongo_adapter.js should have the same functions' names and params, like this:
db.init(client, options, callback)
db.get(client, query, callback)
Now I'm doing it with Underscore's extend function and throwing exceptions if there is no needed function.
Is it ok?
Is there any other approaches to force several Node.js modules to follow the one contract?
server.js, Entry point
var _ = require('underscore');
var mongoose = require('mongoose'); // Adaptee
var db = require('./db');
var fake_mongodb_adapter_raw = require('./fake_mongodb_adapter');
var fake_mongodb_adapter = _.extend({}, db);
fake_mongodb_adapter = _.extend(fake_mongodb_adapter, fake_mongodb_adapter_raw);
mongoose.connect('connection string');
fake_mongodb_adapter.init(mongoose, options, function run_app(err, person_model) {
fake_mongodb_adapter.get(person_model, { _id: '1234' }, function (err, found_person) {
console.log('found person: ' + JSON.stringify(found_person));
});
});
db.js, Generic DB interface used by the client, it shouldn't be changed.
module.exports = {
init: init,
get: get
};
function init(client, options, callback) {
throw new Error('Function "init" not implemented');
}
function get(client, query, callback) {
throw new Error('Function "get" not implemented');
}
fake_mongodb_adapter.js
module.exports = {
init: init,
get: get
};
function init(client, options, callback) {
console.log('initialized fake MongoDB adapter');
return callback(null);
}
function get(client, query, callback) {
var person = { id: '1234', name: 'Mary' };
return callback(null, person);
}

Sinon.js- Trying to spy on console.log but it is already wrapped

I am new to writing node tests.... this is a my first unit test for a gulp plugin I am trying to write:
var chai = require('chai');
var spy = require('chai-spies');
var es = require('event-stream');
var File = require('vinyl');
var mock = require('mock-fs');
var sinon = require('sinon');
var sinonChai = require("sinon-chai");
//var rewire = require("rewire");
//var myModule = rewire("./test.js");
var es = require('event-stream');
chai.should();
chai.use(sinonChai);
describe('gulp-newy', function() {
var fs = require('fs');
var fakeFile, pspy;
beforeEach(function() {
//myModule.__set__('__dirname', "/home/one");
mock({
__dirname: mock.directory({
mode: 0755,
items: {
file1: 'file one content',
file2: new Buffer([8, 6, 7, 5, 3, 0, 9])
}
})
});
});
afterEach(mock.restore);
describe('get files', function() {
it('should do something', function(done) {
mock({
foo: mock.file({
content: 'nothing',
mtime: new Date(Date.now())
}),
bar: mock.file({
content: 'nothing',
mtime: new Date(1,1)
})
});
fakeFile = new File({
contents: new Buffer('foo'),
history: ['foo']
});
var bar = function(dest) { return 'bar' };
spy1 = sinon.spy(console, "log");
stream = newy(bar);
stream.write(fakeFile);
stream.on('data', function() {
console.log("sss");
});
spy1.should.have.been.called();
done();
});
});
});
I get TypeError: Attempted to wrap log which is already wrapped, but I don't see where it was previously wrapped before my spy.
I was using Mocha --watch...which session never ends. Because of this.. the other spy existed. Answer is Cleaning up sinon stubs easily
With sinon you need to make sure you restore your stubs\mocks after every test. if you don't they remain stubs and if you try and stub them again in a different test it will shout about trying to wrap a method again.
There are many ways to do this, contain all of the stubs within a sandbox, or just restore in the "after" clause.
for example:
describe('This is a test', ()=> {
before(()=> {
sinon.stub(myObject,'myMethod', ()=> {
return 'stubbed result';
})
});
it('should stub my method', ()=>{
expect(myObject.myMethod).to.be.equal('stubbed result');
});
after(()=> {
//important part
myObject.myMethod.restore();
});
})

Categories

Resources