Mongoose Models & Integration Testing Race Conditions - node.js

Haven't been able to find any answers to these questions through Twitter or the Mongoose JS Gitter channel and would appreciate some help.
I'm writing an API using a Hapi.JS and Mongoose. I'm using a test database for my integration tests. What I've found though is that if I clear the database after more than one describe block it negatively effects my ability to save and run queries in subsequent describe blocks. I'll leave some annotated code below.
How can I clear the database after each test and not have any race conditions that effect other tests?
'use strict'
//this test will pass when run alone
// it clears the db at the end of it's run
let testConfig = require('../fixtures/fixtures.js')
let User = require('../../models/user.js')
let Bucket = require('../../models/bucket.js')
let BucketFactory = require('../../factories/bucket-factory.js')
let request = require('request')
let bluebird = require('bluebird')
let mongoose = require('mongoose')
mongoose.Promise = bluebird
describe('Buckets API', function() {
it('should get all buckets', function(done){
request.get(`${testConfig.testConfig.testUrl}/buckets`, (err, response, body)=>{
if (err){ throw new Error(err)}
expect(response.statusCode).toBe(200)
done()
})
})
it('should get a bucket by its id', function (done) {
request.get(`${testConfig.testConfig.testUrl}/buckets/${mongoose.Types.ObjectId()}`, (err, response, body)=>{
expect(response.statusCode).toBe(200)
done()
})
});
it('should post it', function (done) {
let testBucket = {name: "Drill", userId: mongoose.Types.ObjectId()}
request.post(`${testConfig.testConfig.testUrl}/buckets`, {json: testBucket}, (err, response, body)=>{
if(err){ throw new Error(err)}
expect(response.statusCode).toEqual(200)
done()
} )
});
// i dont need this afterEach
// but for illustrative purposes it will mess up the latter test
// which will pass if i run it by itself
// but it shouldnt right?
afterEach((done)=>{
Bucket.remove({}).then(()=>{done()})
})
})
describe('findByAndUpdate', function () {
beforeEach((done)=>{
// this factory creates and saves a bucket
// i've verified this by checking the test database manually
let newBucket = BucketFactory
done()
})
it('should find and update', function (done) {
Bucket.find({}).exec()
.then((data)=>{
request.put(`${testConfig.testConfig.testUrl}/buckets/${data._id}`, {json: {name: 'Marvel'}}, (err, response, body)=>{
if(err){ throw new Error(err)}
console.log(body)
expect(response.statusCode).toEqual(200)
done()
} )
})
});
afterEach((done)=>{
Bucket.remove({}).then(()=>{done()})
})
});

Related

Can set data in Redis server but can't get it

I can set the value for a key in my Redis server (when I flushall, run this code, and then get key in redis-cli, I get back the proper values), but when I try to get key values through my NodeJs server, it never even logs out 'got data'.
I considered maybe this was because these functions were running asynchronously, and I was asking to get values that weren't yet stored in the cache, but that does not explain why it wouldn't print 'got data' ever.
My console logs-> 'start'->'data saved'->'end' (and no 'got data', ever)
In redis-cli-> flushall->get test->(nil)->run app.js(in the other terminal)->get test->"1, 2, 3, 4, 5"
I get no errors at all, the code runs, but does not do what I want it to.
Also, don't know if this is relevant, but when connecting to the Redis server, just Redis.createClient() only created a client but did not connect and when I looked it up, the general idea I got was that newer versions did not connect automatically and you had to manually redisClient.connect().
I struggled with this a bit at the start but seemed to have sorted this problem, but just thought I'd mention it, if I messed up somehow, please correct me, as I'm pretty new to NodeJs and codng in general.
My code:
const redisClient = Redis.createClient();
redisClient.connect();
const data = [1, 2, 3, 4, 5];
app.get('/', async(req, res, next) => {
console.log('start')
await redisClient.set('test', data);
console.log('data saved');
await redisClient.get('test', (error, test)=>{
console.log('got data');
console.log(test);
});
console.log('end');
});
Thanks!
I have seen your code. Based on my investigation you should remove the callback and keep await only while you get the data from redis.
I have investigated more this issue and have found that client.get() and client.set() function runs asynchronously. Hence it would achieve this way.
client.set('foo', 'bar', (err, reply) => {
if (err) throw err;
console.log(reply);
client.get('foo', (err, reply) => {
if (err) throw err;
console.log(reply);
});
});
But everytime is not the same use-case that we should set and get the value immediately.
To get rid of this, Following are the options.
Promises and async/await
you can promisify a subset of node_redis functions one at a time using native Node.js promises and util.promisify:
example:
const redis = require('redis');
const { promisify } = require('util');
const runApplication = async () => {
const client = redis.createClient();
const setAsync = promisify(client.set).bind(client);
const getAsync = promisify(client.get).bind(client);
await setAsync('foo', 'bar');
const fooValue = await getAsync('foo');
console.log(fooValue);
};
I have used the await here and solve an issue. In addition to that you can use redis.get().then() also to fetch the data rather than a callback.
I am also attaching the link with an example provided by redis repo
https://github.com/redis/node-redis/blob/master/examples/connect-as-acl-user.js
Following is the code, I have tested and it is working fine now.
redis.js
const redis = require("redis");
const redisClient = redis.createClient({
url: "redis://host:6379",
password: "password",
});
redisClient.connect();
// const { promisify } = require("util");
// promisify(redisClient.get).bind(redisClient);
// promisify(redisClient.set).bind(redisClient);
module.exports = redisClient;
index.js
const express = require("express");
const app = express();
const redisClient = require("./redis");
app.get("/set", async (req, res, next) => {
try {
const data = req.query.p;
await redisClient.set("test", data);
res.status(200).json({
message: "data cached",
data: data,
});
} catch (err) {
console.error(err);
res.status(500).json({
message: "Something went wrong",
});
}
});
app.get("/get", async (req, res, next) => {
try {
// const data = await redisClient.get("test");
const data = await reddisClient.get("test").then((data) => {
return data;
});
res.status(200).json({
message: "Cached data retrieved",
data,
});
} catch (err) {
console.error(err);
res.status(500).json({
message: "Something went wrong",
});
}
});
app.listen(process.env.PORT || 3000, () => {
console.log("Node server started");
});
Please find attached a screenshot of the output.
So the final thought is that, when we are using callback and wants to execute the code synchronously you should either use callback inside callback (but it is created callback hell, so it would not suggested anymore) or you should use promise/async await/native promisify library of nodejs.
Please visit below link to get the simplest understanding and example.
https://docs.redis.com/latest/rs/references/client_references/client_nodejs/
Hope my question clear your mind. I am happy to accept the relevant suggestion to improve an answer.

How to restore mongo in chai mocha

So I want to restore mongo database before tests begin.
I do this way:
const app = require("../app");
const chai = require("chai");
const mongoose = require("mongoose");
const User = require('../models/users');
const Region = require('../models/regions');
const testUsers = require('../testdata/users.json');
const testRegions = require('../testdata/regions.json');
describe('Restoring database', function () {
before(function(done) {
var promises = [
User.deleteMany().exec()
,Region.deleteMany().exec()
];
console.log('Cleaned database');
done();
});
before(function(done) {
testUsers.users.forEach(element => {
var ObjectId = mongoose.Types.ObjectId;
element._id = new ObjectId(element._id);
var newUser = new User(element);
newUser.save(function (err, result) {
if (err) {
console.log("err:",err);
}
});
});
console.log('Users added');
done();
});
before(function(done) {
testRegions.regions.forEach(element => {
var newRegion = new Region(element);
newRegion.save(function (err, result) {
if (err) {
console.log("err:",err);
}
});
});
console.log('Regions added');
done();
});
testdata/users.json and testdata/regions.json are simple json files including key/pair values.
Does this look good?
When I run
npm test
It does not give any error. And in the console I see this:
Restoring database
Cleaned database
Users added
Regions added
But when I look in database I get different results.
Sometimes everything looks good. All the rows are in the collections.
Sometimes a few rows are missing in one of the collections.
Sometimes one of the collections is empty.
This is a very strange behaviour.
I also tried to add in the variable "promises" each of the "newUser" and "newRegion" instead of executing them directly.
But I still get these strange results.
Whats the deal?
The issue is due to done() being called before the async statements/promises have completed.
Either use async/await or use Promises and only call done() when your async statements/promises have completed.
For example:
No done() call as we using await statements which will wait till each statement completes before continuing:
before(async function() {
let userResult = await User.deleteMany();
let regionResult = wait Region.deleteMany();
console.log('Cleaned database');
});
Or use done() with promises:
before(function(done) {
User.deleteMany()
.then(result => {
console.log('Cleaned database');
done();
});
});
The syntax in your before example is not adding Promises at all, it is simply adding those functions to an array:
var promises = [
User.deleteMany().exec()
,Region.deleteMany().exec()
];
Take a look at the following related answer to help.

Unable to use .then in express framework

I'm new to Express framework and learning, I'm having a problem using .then. The problem is I have 2 functions and I want the first one to complete before the second to start executing. I'm exporting the modules.
var ubm = require('./userBasic');
There are 2 functions setUserData and showUserId, the showUserId must execute only after setUserData has performed its operation.
var userId = ubm.setUserData(userName,userEmail,userDOB,moment);
userId.then(ubm.showUserId(userId));
Below are the 2 functions:
module.exports = {
setUserData: function (userName,userEmail,userDOB,moment){
//Performing database activities
return userId;
}
showUserId: function (userId){
console.log(userId);
}
}
When i run it says TypeError: Cannot read property 'then' of undefined.
Like I said I'm very new and learning and was unable to figure out the solution. I did some google search and got a brief about promise, but I don't know how to implement here.
Try using promises
module.exports = {
setUserData: function(userName, userEmail, userDOB, moment) {
return new Promise(function(resolve, reject) {
//db stuff
reject(error);
resolve(userId);
});
},
showUserId: function(userId) {
console.log(userId);
};
};
So in your execution you would write
ubm.setUserData(username, usereEmail, userDOB, moment)
.then((data) => {
showUserId(data);
})
.catch((err) => {
console.log(err);
});
A couple of things to note is that in this instance you could just log data without the need for another function like
ubm.setUserData(username, usereEmail, userDOB, moment)
.then((data) => {
console.log(data);
})
.catch((err) => {
console.log(err);
});
Whatever value you pass into resolve() will be returned as well as you pass errors into reject().

How to test stubbed database queries

I have a Sails.Js controller that looks like this
module.exports = {
confirmID: function(req,res) {
var uid = req.params.id;
User.findOne({id:uid}).exec(function(err,user) {
// ...
});
}
}
where User is a sails-postgres model. I have tried testing it with mocha, sinon and supertest with a test like this
describe('Controller', function() {
var sandbox;
before(function() {
sandbox = sinon.sandbox.create();
sandbox.stub(User, 'findOne');
});
after(function() {
sandbox.restore();
});
describe('GET /confirmid/:id', function() {
it('should do something', function(done) {
request(sails.hooks.http.app)
.get('/confirmid/123')
.expect(200)
.end(function(err,res) {
sandbox.fakes[0].called.should.be.true;
done();
});
});
});
If I leave it at that it errors out because exec is called on undefined, but I can't seem to stub the nested exec method without either errors or the test hanging. Is there a way to stub a series of method calls such as .find().exec()? Or am I best to just leave this to integration tests where I can test it with an actual database?
Assuming that you really want to stub (not just spy) - you want to control what the query resolves to as opposed to simply knowing whether the query was executed. Here's what I'm using to stub sails/waterline query methods. Something like...
var stubQueryMethod = require('stubQueryMethod');
describe('Controller', function() {
before(function() {
stubQueryMethod(User, 'findOne', {
id: 123,
name: 'Fred Fakes'
});
});
after(function() {
User.findOne.restore();
});
describe('GET /confirmid/:id', function() {
it('should do something', function(done) {
request(sails.hooks.http.app)
.get('/confirmid/123')
.expect(200)
.end(function(err,user) {
user.should.have.property('name', 'Fred Fakes');
done();
});
});
});
});
Source: https://gist.github.com/wxactly/f2258078d802923a1a0d
For people looking for other options to stub or mock waterline models, I've found the following four options:
stubQueryMethod.js gist - https://gist.github.com/wxactly/f2258078d802923a1a0d
model mock gist - https://gist.github.com/campbellwmorgan/e305cc36365fa2d052a7
weaselpecker - https://github.com/ottogiron/weaselpecker
sails-mock-models - https://github.com/ryanwilliamquinn/sails-mock-models
After evaluating each one, I've decided on sails-mock-models because it is easy to understand and seems the most used sails mocking library according to npm: https://www.npmjs.com/package/sails-mock-models
Hope this helps someone!
Update: I'm still using sails-mock-models, and it is quite easy, but there are a few drawbacks such as it fails to return promises that are taken into a q.all(promiseArray).then() call. If I get around to investigating the other options or find a workaround, I will post it here.
This will only work for queries that use exec and it overloads all exec calls so if you try to return an error and you have, say, a controller with a policy out front, and the policy does a database lookup, you'll likely go into error there prior to hitting the controller code you intended to test.... that can be fixed with stub.onCall(x), but it is still a bit precarious.
Warnings aside, here's how I've done this in the past:
var path = require('path');
var sinon = require('sinon');
var Deferred = require(path.join(
process.cwd(),
'node_modules/sails',
'node_modules/waterline',
'lib/waterline/query/deferred'
));
module.exports = function () {
return sinon.stub(Deferred.prototype, 'exec');
};
Assuming you have the following service, MyService:
module.exports.dbCall = function (id, cb) {
Model.findOne(id).exec(function (err, result) {
if (err) {
sails.log.error('db calls suck, man');
return cb(err, null);
}
cb(null, result);
});
};
You can test the error case like so:
before(function () {
stub = databaseStub();
});
afterEach(function () {
stub.reset();
});
after(function () {
stub.restore();
});
it('should return errors', function (done) {
stub.onCall(0).callsArgWith(0, 'error');
MyService.dbCall(1, function (err, results) {
assert.equal(err, 'error');
assert.equal(results, null);
done();
});
});

Using Supertest in Mocha to test Node.js Express API and MongoDB

I have been searching this site and the web for a while now and I cannot find a solution to this problem. I am trying to test the REST function of my API, but the PUT test never seems to work. Each time the test runs in mocha, I get the error "Uncaught assertion error: expected [] to equal {objectData}" where objectData is the json representation of the object I am trying to post (named couponTwo).
I have a feeling the problem lies in the beforeEach function, as it clears the database before each test, which needs to be done for many other tests to run correctly. Here is the test code:
var config = require('../config/config');
var mongoose = require('mongoose');
var should = require('should');
var request = require('supertest');
var Coupon = require('../models/coupon');
var url = require('../config/config').test.url;
process.env.NODE_ENV = 'test';
beforeEach(function (done) {
function clearCollections() {
for (var collection in mongoose.connection.collections) {
mongoose.connection.collections[collection].remove(function() {});
}
return done();
}
if (mongoose.connection.readyState === 0) {
mongoose.connect(config.test.db, function (err) {
if (err) throw err;
return clearCollections();
});
} else {
return clearCollections();
}
});
afterEach(function (done) {
mongoose.disconnect();
return done();
});
Here is the that is supposed to test that an object exists in the database after a PUT:
describe('#post', function () {
it('should return a coupon object after post', function (done) {
request(url).post('/coupons')
.set('Content-Type', 'application/json')
.send(couponTwo)
request(url).get('/coupons').end(function (err, res) {
if (err) throw err;
console.log(res.body);
res.body.should.eql(couponTwo);
done();
})
})
})
I apologize if the answer to this question is obvious and I am missing something fundamental, but I have reached a roadblock. Thanks for your help!
I think it is because of the asynchronous nature of request calls. You need to wrap the second request in a callback, so that it will only be executed when the first one is completed and your test object is put into the database.
Also, .eql(couponTwo) will fail in your case anyway, because your response is an array containing the object that was put, and you compare it directly to the object. Use .eql([couponTwo]) if you want to make sure that it is the only element in the array, or just use .containEql(couponTwo).
Try this:
request(url).post('/coupons')
.set('Content-Type', 'application/json')
.send(couponTwo)
.end(function () {
request(url).get('/coupons').end(function (err, res) {
if (err) throw err;
console.log(res.body);
res.body.should.containEql(couponTwo);
done();
});
});

Resources