Unit testing with Supertest, Mocha & Sinon timing out - node.js

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).

Related

Express error handling between model and routes

I have a router file which handles all the routes for country in express and call a function from Model file.
router.get('/:_id', function(req, res, next){
countriesModel.getCountry(req.params._id, function(data, err){
if(err)
{
res.json({status:0, message:"Country Not Found for id : "+req.params._id, errDetails:{err}});
res.end();
}
else
{
res.json(data);
res.end();
}
}); });
And here is the getCountry Function from model file.
exports.getCountry = function(id, callback){
return db.queryAsync('select * from tbl_countries where ID = '+id)
.then(function(countryRows){
if(countryRows.length){
return Promise.resolve(callback(countryRows));
}
else
{
return Promise.resolve(callback('No Data To Return.'));
}
});
}
It works fine when i enter correct id, however i want to push error when someone enters wrong id which is not available in database.
Can you please guide me how i can achieve this, I am new to Node & Express.
I am using mySQL with express.
First off, since your database is already returning a promise, you can just make your function return a rejected promise when there's an error condition. And, stop using plain callbacks at all. You already have a promise, let your caller use that:
exports.getCountry = function(id){
return db.queryAsync('select * from tbl_countries where ID = '+id)
.then(function(countryRows){
if(countryRows.length){
// make countryRows be resolved value of the promise
return countryRows;
} else {
// make promise be rejected with this error
throw new Error('Country not found'));
}
});
}
Then, in the router, use the returned promise:
router.get('/:_id', function(req, res, next){
countriesModel.getCountry(req.params._id).then(data => {
res.json(data);
}).catch(err => {
res.json({status:0, message:"Country Not Found for id : "+req.params._id, errDetails:{err}});
});
});
Notes:
There are other reasons you can get a rejected promise here (such as some sort of database error). You have to decide what you want to return in your route when that happens. Right now, it returns the country not found for all errors, but those other types of errors should probably return a 5xx status.
There is no reason to call res.end() after a res.send() or a res.json() as it is called automatically for you.
Never mix promises with plain callbacks. If you have a promise already at the lowest level, just use it and don't cover it with a plain callback.

Why am I getting a warning when using express app.param to pre-load object with sequelize?

I'm using express app.param to load objects from db (app.param('userId', users.userById);):
exports.userById = function (req, res, next, id) {
return user.findOne({
where: { id: id }
}).then(function (result) {
req.user = result;
next();
}).catch(function (error) {
next(error);
});
};
After that I update the loaded object with the following code.
exports.update = function (req, res) {
var user = req.user;
return user.update({
//update properties
}).then(function () {
res.end();
}).catch(function (error) {
//error handling
});
};
For some reason I get the warning that "a promise was created in a handler but was not returned from it".
I can't see why, but that always happen when I use a routing parameter that uses sequelize before making the actual changes to the database.
What is the correct way to do this?
I'm using sequelize v3.23.3 with Postgres.
EDIT
I changed the code to a more simple example that throws the same warning.
If you forget to return that request promise, the next handler executes immediately with an argument of undefined - which is completely valid for the Promises/A+ spec, but I don't think that it is what you are looking for.
See How to execute code after loop completes for solutions.

Express middleware testing with race condition

I'm having a trouble testing a middleware in Express that utilises promise (using Q library).
Here's an example that resembles my case.
var httpMocks = require('node-mocks-http');
test('should return 404 status', function (done) {
var req = httpMocks.createRequest(),
var res = httpMocks.createResponse();
myMiddleware(req, res);
expect(req.statusCode).to.equal(404);
});
Let's say, myMiddleware makes a call to another module using promise that calls either next() on resolve or res.status(404).send() on reject.
I mocked the module for testing purpose.
how would I able to catch the end of myMiddleware on reject that does not return with next()?
Cheers!
You're going to have to change myMiddleware to either return a promise, or making a callback.
You could do something like this:
// myMiddleware now makes callback with potential error
function myMiddleware(req, res, callback) {
async.map(..., function(err, results){
callback(err);
});
}
Then in your test you can wait for the callback to test values:
test('should return 404 status', function (done) {
var req = httpMocks.createRequest(),
var res = httpMocks.createResponse();
myMiddleware(req, res, function(error) {
if(error) {
// Handle error for tests
}
// Test your values now
expect(req.statusCode).to.equal(404);
// All done
done();
});
});
In this example the callback from myMiddleware only returns an error, because apparently you're not concerned with anything else. In your case, we are mainly just using the callback as a trigger to test the values.

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 do I sinon.stub a nested method with a callback?

I need to test a method that includes a sub-method which makes a call to an API server. I’d like to stud this internal sub-method, but I can’t seem to do that. Here’s an example:
var requests = require('./requests.js');
var utilityClass = {
methodCreatesObject: function (callback) {
// Here’s the method I’m trying to stub:
requests.makeCallToAPI(requestObject, function (err, responseFromAPI) {
doSomethingWithResponse(responseFromAPI, function (err, finalObject) {
if (err) {
callback(err, null);
} else {
callback(null, finalObject); // <- Want to test the value of finalObject
}
});
});
}
}
So, my test looks something like this (updated to show loading requests.js before utility.js):
var should = require('should'),
Joi = require('joi'),
sinon = require('sinon'),
requests = require('../lib/modules/requests.js'),
utility = require('../lib/modules/utility.js')
;
// Start my tests:
describe('Method', function () {
before(function () {
var fakeAPIresponse = { ... }
sinon.stub(requests, 'makeCallToAPI').yield(null, fakeAPIresponse);
});
it('should produce a well-formed finalObject', function (done) {
utilityClass.methodCreatesObject(function (err, response) {
if (err) {
done(err);
} else {
response.should.do.this.or.that;
done();
}
});
});
});
As I understand it, .yields() should try to run the first callback it detects in the arguments and feed its own arguments to it (resulting in doSomethingWithResponse(responseFromAPI, function () {...})). However, when running mocha, I’m getting an error indicating that the API server could not be reached, which suggests that the real requests.makeCallToAPI() is being called, and not my stub.
I must be missing something. What am I doing wrong here?
Where are you requiring the request.js? You will need to require request.js before you load up the module you want to test.
Edit 1: Using sinon.js
Here is a gist of what I meant: https://gist.github.com/limianwang/1114249de99c6a189384
Edit 2: Using proxyquire
If you are intending to test simply the utilities without concern of what actually happens within the requests.makeAPICall, you can use something like proxyquire to do the trick. If you are concerned with the actual logic within requests.js, you can use sinon.stub to stub out the actual request.get api.

Resources