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();
});
});
Related
I have an application using Node.js/Express. Within this code I have the following promise designed to check if an email already exists in my (PostGres) database:
//queries.js
const checkEmail = function(mail) {
return new Promise(function(resolve, reject) {
pool.query('SELECT * FROM clients WHERE email = $1', [mail], function(error, results) {
if (error) {
reject(new Error('Client email NOT LOCATED in database!'));
} else {
resolve(results.rows[0]);
}
}) //pool.query
}); //new promise
}
In my 'main (server.js)' script file, I have a route which is called upon submission of a 'signup' form. When the post to this route is processed...I run the script above to check if the passed email address is already located in the database, along with various other 'hashing' routines:
My code is as follows:
//server.js
const db = require('./queries');
const traffic = require('./traffic');
const shortid = require('shortid');
...
app.post('/_join_up', function(req, res) {
if (!req.body) {
console.log('ERROR: req.body has NOT been returned...');
return res.sendStatus(400)
}
var newHash, newName;
var client = req.body.client_email;
var creds = req.body.client_pword;
var newToken = shortid.generate();
var firstname = req.body.client_alias;
db.sanitation(client, creds, firstname).then(
function(direction) {
console.log('USER-SUPPLIED DATA HAS PASSED INSPECTION');
}
).then(
db.checkEmail(client).then(
function(foundUser) {
console.log('HEY THERE IS ALREADY A USER WITH THAT EMAIL!', foundUser);
},
function(error) {
console.log('USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...');
}
)).then(
traffic.hashPassword(creds).then(
function(hashedPassword) {
console.log('PASSWORD HASHED');
newHash = hashedPassword;
},
function(error) {
console.log('UNABLE TO HASH PASSWORD...' + error);
}
)).then(
traffic.hashUsername(firstname).then(
function(hashedName) {
console.log('NAME HASHED');
newName = hashedName;
},
function(error) {
console.log('UNABLE TO HASH NAME...' + error);
}
)).then(
db.createUser(client, newName, newHash, newToken).then(
function(data) {
console.log('REGISTERED A NEW CLIENT JOIN...!!!');
res.redirect('/landing'); //route to 'landing' page...
},
function(error) {
console.log('UNABLE TO CREATE NEW USER...' + error);
}
))
.catch(function(error) {
console.log('THERE WAS AN ERROR IN THE SEQUENTIAL PROCESSING OF THE USER-SUPPLIED INFORMATION...' + error);
res.redirect('/');
});
}); //POST '_join_up' is used to register NEW clients...
My issue is the '.then' statements do not appear to run sequentially. I was under the impression such commands only run one after the other...with each running only when the previous has completed. This is based upon the logs which show the readout of the 'console.log' statements:
USER-SUPPLIED DATA HAS PASSED INSPECTION
PASSWORD HASHED
NAME HASHED
UNABLE TO CREATE NEW USER...Error: Unable to create new CLIENT JOIN!
USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...
As mentioned previously, I am under the impression the '.then' statements should run synchronously, therefore the last statement ("USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...") should in fact be after the first...before the "PASSWORD HASHED" according to the layout of the '.then' statements. Is this normal behavior...or do I have an error in my code?
Sorry for my confusion however I find '.then' statements and promises to be somewhat confusing for some reason. I thank you in advance.
TLDR - You must pass a function reference to .then() so the promise infrastructure can call that function later. You are not doing that in several places in your code.
A more specific example from your code:
You have several structures like this:
.then(db.createUser().then())
This is incorrect. This tells the interpreter to run db.createUser() immediately and pass its return result (a promise) to .then(). .then() will completely IGNORE anything you pass is that is not a function reference and your promises will not be properly chained.
Instead, you must pass a function reference to .then() something like this (not sure what execution logic you actually want):
.then(() => { return db.createUser.then()})
Then main point here is that if you're going to sequence asynchronous operations, then you must chain their promises which means you must not execute the 2nd until the first calls the function you pass to .then(). You weren't passing a function to .then(), you were executing a function immediately and then passing a promise to .then(p) which was completely ignored by .then() and your function was executed before the parent promise resolved.
FYI, sequencing a bunch of asynchronous operations (which it appears you are trying to do here) can take advantage of await instead of .then() and end up with much simpler looking code.
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.
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.
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).
I'm using the Sails.js MVC and I'm trying to setup a service so I can make a call to an Active Directory server and pass the data for a user back to my controller.
I'm using some internal company modules for this which connect to our servers and pass back a user array with all the data for a selected user.
If I do this by making a function directly in the API controller it works fine, but when doing it by calling through a function from a separate file, rather than returning an array of [Function: bound].
Code from controller (LabController.js):
var adGet = require('../services/adGet');
module.exports = {
test: function (req, res) {
console.log(adGet.userData);
res.view({
user: adGet.userData
});
}
}
Code from the service (adGet.js):
module.exports = {
userData: function (req, res) {
var ad = require('active-directory');
ad.setCredentials({
user: 'username_removed',
password: 'password_removed'
});
ad.getUser(req.session.sisso.idsid).then(function (user) {
return (user);
});
}
}
Any help is greatly appreciated.
There's a few issues here.
First, you're trying to use return in your userData service method to return data, but it's an asynchronous function so that return statement is sending the data anywhere. You need to pass in a callback function as an argument to userData that can be called when the data is ready (i.e. when the database query returns):
module.exports = {
// note the new `cb` argument
userData: function (req, res, cb) {
var ad = require('active-directory');
ad.setCredentials({
user: 'username_removed',
password: 'password_removed'
});
ad.getUser(req.session.sisso.idsid)
.then(function (user) {
// Still nice to use `return` to make sure nothing
// gets executed after this statement, but it's the
// callback function that actually gets the data.
// Note the `null` first argument, indicating no errors.
return cb(null,user);
})
.catch(err) {
return cb(err);
});
}
}
Second, you're sending the adGet.userData function to your view as a local variable, but you're not actually calling it to get the data. And since it's an asynchronous function, you won't be able to call it from your view. You need to call it from within the controller and send the result to the view:
var adGet = require('../services/adGet');
module.exports = {
test: function (req, res) {
// Call service function, passing callback function in as argument
adGet.userData(req, res, function(err, user) {
// Handle errors
if (err) {return res.serverError(err);}
// If all goes well, send user data to view
return res.view({
user: user
});
});
}
}
Less importantly, you could refactor the userData method to not accept req and res as arguments--it's overkill. Save req and res for your controllers whenever possible. It would be better to have userData just expect userId and callback as arguments. Also, unless you've turned global services off using the config/globals.js file, you don't need to require the services file at the top of your controller; the adGet variable will be made available to you automatically.