Mocha: using it-each with dependent variables - node.js

So I've been having some trouble with the it-each module for loop testing in mocha. I'm using supertest, should and it-each modules.
var hasAccess = [{endpoint: "announcements", send: {"creator": users}}]
users is my varibale which is assigned in this test
it('users', function(done) {
api.get('/users')
.set('Accept', 'application/json')
.set('Authorization', devToken)
.expect(200)
.expect('Content-Type', /json/)
.end(function(err, res) {
if (err) return done(err)
res.body[0].should.have.property('id')
users = res.body[0].id //<----------
done()
});
});
My it-each
it.each(hasAccess, 'should return 201 perfoming a POST on %s', ['endpoint'], function(element, next) {
console.log(element.send) //prints out "creator": undefined
api.post('/' + element.endpoint)
.set('Accept', 'application/json')
.set('Authorization', userToken)
.expect('Content-Type', /json/)
.send(element.send)
.expect(201)
.end(function(err, res) {
if (err) return next(err)
res.status.should.equal(201)
next()
});
});
Problem is, hasAccess is being passed into the it-each before users is assigned a value. I know it's bad practice to make tests dependent on each other, but there isn't really another way to test this. I need the id to completely test this. So any ideas?
Also there will be more objects in hasAccess, so a loop is the best way to do it. Just shortened it for this.

You really shouldn't make tests dependent on one another, but if you must...
The problem is not with it.each but with how you are storing the user id you get in your first test. You create your array like this:
var hasAccess = [{endpoint: "announcements", send: {"creator": users}}]
This takes the value from users for the hasAccess[0].send.creator field. It is undefined at that point so the field gets the value undefined. From this point on, the changes you do to users won't affect the values stored in hasAccess. If users were an object, then hasAccess would have a reference to that object and modifying the object would be reflected in hasAccess but it is a primitive value: undefined. So further changes to users have no effect here.
In order for the change to carry forward, your first test could get to the field by going through the hasAccess array.
Here's an illustration of how it could be done. I've simulated the asynchronous processes with setTimeout:
require("it-each")();
var assert = require("assert");
var users;
var hasAccess = [{endpoint: "announcements", send: {"creator": users}}];
it("get users", function (done) {
setTimeout(function () {
// Wrong way:
// users = "foo";
// Right way:
hasAccess[0].send.creator = "foo";
done();
}, 1000);
});
it.each(hasAccess, 'should return 201 perfoming a POST on %s', ['endpoint'],
function(element, next) {
setTimeout(function () {
assert(element.send.creator === "foo");
// console.log(element);
next();
}, 1000);
});

Related

Mongoose Models & Integration Testing Race Conditions

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()})
})
});

Unit testing with Supertest, Mocha & Sinon timing out

I am trying to write a unit/integration test where I want to get a list of things in the database. For not it is only a GET, but these tests needs to extend to POST, PUT & DELETE.
The code I have thus far works fine, I can actually get data from the DB, but as soon as I try to stub out the function which is responsable for making the call to the DB, Mocha times out
1 failing
1) /account_types GET 200 List:
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.
at null. (C:\Code\JS\general_admin_service\node_modules\mocha\lib\runnable.js:215:19)
I understand the done() callback isn't being called because the code is stuck somewhere, however, I do not understand what I am doing wrong.
I used the following references to get where I am:
Testing With Mocha, Sinon.js & Mocking Request
Lessons learned from unit testing with Sinon.JS
My code is as follows:
The Test:
'use strict';
var expect = require('chai').expect,
request = require('supertest'),
chance = require('chance').Chance(),
server = require('../server'),
sinon = require('sinon'),
select = require('../../helpers/data_access/select');
describe("/account_types", function () {
before(function(done){
sinon
.stub(select, "query_list")
.returns([{id: "test"}]);
done();
});
after(function(done){
select
.query_list
.restore();
done();
});
it('GET 200 List', function (done) {
request(server.baseURL)
.get('/api/v1/account_types')
.set('Accept', 'application/json')
.expect('Content-Type', 'application/json')
.expect(200)
.end(function (err, res) {
/* istanbul ignore if */
if (err)
return done(err);
expect(res.body).to.include.keys('result');
expect(res.body.result).to.not.be.null;
expect(res.body.result).to.not.be.undefined;
expect(res.body.result).to.be.an('Array');
expect(res.body.result.length).to.be.above(0);
//expect(select.query_list).to.have.been.calledOnce;
return done();
});
});
});
Restify endpoint:
var select = require('../helpers/data_access/select')
module.exports = function (server) {
var query = "..."
return select.query_list(res, next, db_config, query);
});
};
select.js:
var sql = require('mssql');
module.exports = {
query_list: function (res, next, config, sql_query) {
return query(res, next, config, sql_query, true);
},
query_single: function (res, next, config, sql_query) {
return query(res, next, config, sql_query, false);
}
};
function query(res, next, config, sql_query, isList) {
var connection = new sql.Connection(config);
connection.connect(function (err) {
if (err) {
return on_error(err, res);
}
var request = new sql.Request(connection);
request.query(sql_query, function (err, response) {
connection.close();
if (err) {
return on_error(err, res);
}
if (isList) {
return return_list(res, response, next);
} else {
return return_single(res, response, next);
}
});
});
}
function on_error(error, res, next) {
res.status(500).send(error);
return next();
}
function return_list(res, response, next) {
res.send({result: response});
return next();
}
function return_single(res, response, next) {
res.send({result: response[0]});
return next();
}
What I expect to happen is that because I stub out the query_list function, should I wish to put a console.log(res.body.result); after the expect's I have in place, I should see a return of [{id: "test"}], but it is obviously not getting to that point.
What am I doing wrong?
UPDATE: Added the full select.js file.
As you already make clear in the comments, it's difficult to test code that's deeply nested.
It's usually much better to work with callbacks or promises, so that each piece of your app will handle the part it's responsible for, but not (much) more. So your route handler will handle the request and the response. It's obviously okay to call other functions, like ones that perform database queries, but instead of letting those functions send back a response, you use callbacks that "call back" to the route handler with the query results.
Something like this:
server.get('/api/v1/account_types', function(req, res, next) {
select.query_list(QUERY, function(err, records) {
if (err) return next(err);
res.send({ results : records });
next();
});
});
In terms of using Sinon to test something like this: it really depends on the exact implementation. I can provide a quick example on how to stub the above usage of select.query_list, to make sure that the response contains the correct data.
The basic stub looks like this:
sinon.stub(select, 'query_list').yieldsAsync(null, [ { id : 'test' } ]);
What this does, is when select.query_list() gets call, it will call the first callback argument it receives (it does this by checking each argument to see which is a function) with the arguments null, [ { id : 'test' } ].
Those are the err and records arguments of the callback function passed in the handler. So you can use this to skip the database query entirely and pretend that the query yielded a particular array of records.
From there, res.send() gets called (which was the issue that you initially ran into: it didn't get called at all because it was being performed in a part of your app that wasn't getting called because of your stub) and you can check in your test if the resulting response data is as expected.
It becomes a bit more complicated if you want to stub a function deeper in the call stack, but with the correct Sinon tools (like .yields*, or using spies instead of stubs) it's usually not terribly difficult (provided that all the functions that you want to stub/spy are accessible, that is, exported).

Testing Express and Mongoose with Mocha

I'm trying to test my REST API endpoint handlers using Mocha and Chai, the application was built using Express and Mongoose. My handlers are mostly of the form:
var handler = function (req, res, next) {
// Process the request, prepare the variables
// Call a Mongoose function
Model.operation({'search': 'items'}, function(err, results) {
// Process the results, send call next(err) if necessary
// Return the object or objects
return res.send(results)
}
}
For example:
auth.getUser = function (req, res, next) {
// Find the requested user
User.findById(req.params.id, function (err, user) {
// If there is an error, cascade down
if (err) {
return next(err);
}
// If the user was not found, return 404
else if (!user) {
return res.status(404).send('The user could not be found');
}
// If the user was found
else {
// Remove the password
user = user.toObject();
delete user.password;
// If the user is not the authenticated user, remove the email
if (!(req.isAuthenticated() && (req.user.username === user.username))) {
delete user.email;
}
// Return the user
return res.send(user);
}
});
};
The problem with this is that the function returns as it calls the Mongoose method and test cases like this:
it('Should create a user', function () {
auth.createUser(request, response);
var data = JSON.parse(response._getData());
data.username.should.equal('some_user');
});
never pass as the function is returning before doing anything. Mongoose is mocked using Mockgoose and the request and response objects are mocked with Express-Mocks-HTTP.
While using superagent and other request libraries is fairly common, I would prefer to test the functions in isolation, instead of testing the whole framework.
Is there a way to make the test wait before evaluating the should statements without changing the code I'm testing to return promises?
You should use an asynchronous version of the test, by providing a function with a done argument to it.
For more details refer to http://mochajs.org/#asynchronous-code.
Since you don't want to modify your code, one way to do that could be by using setTimeout in the test to wait before to call done.
I would try something like this:
it('Should create a user', function (done) {
auth.createUser(request, response);
setTimeout(function(){
var data = JSON.parse(response._getData());
data.username.should.equal('some_user');
done();
}, 1000); // waiting one second to perform the test
});
(There might be better way)
Apparently, express-mocks-http was abandoned a while ago and the new code is under node-mocks-http. Using this new library it is possible to do what I was asking for using events. It's not documented but looking at the code you can figure it out.
When creating the response object you have to pass the EventEmitter object:
var EventEmitter = require('events').EventEmitter;
var response = NodeMocks.createResponse({eventEmitter: EventEmitter});
Then, on the test, you add a listener to the event 'end' or 'send' as both of them are triggered when the call to res.send. 'end' covers more than 'send', in case you have calls other than res.send (for example, res.status(404).end().
The test would look something like this:
it('Should return the user after creation', function (done) {
auth.createUser(request, response);
response.on('send', function () {
var data = response._getData();
data.username.should.equal('someone');
data.email.should.equal('asdf2#asdf.com');
done();
});
});

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