Reputation: 1050
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:
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.
Upvotes: 0
Views: 3890
Reputation: 203504
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).
Upvotes: 1