I'm trying to build a simple login module for Node. I'm trying to do it in a TDD way, but I'm still new to it, so any tips or resources that will help me understand it better would be great.
My problem comes when I query a database with invalid data, and I'm expecting an error. The error is thrown if I test the app manually - which is great. However when I try to test it with Mocha and Expect.js, I get Error: expected fn to throw an exception. If I switch the code from to.throwError() to to.not.throwError() the errors are thrown properly. I think the problem is coming in somewhere with my attempts at asynchronous tests and error handling. The first test passes fine.
Thanks for taking a look.
New code based on SebastianG's instructions
login.js
var MongoClient = require('mongodb').MongoClient;
exports.login = function(callback, email, password) {
MongoClient.connect("mongodb://localhost:27017/stockalertDev", function(err, db) {
if (err) {
return err;
}
var collection = db.collection('users');
if (email) {
collection.findOne({email:email}, function(err, item) {
try {
if (err) {
console.log('error');
throw new Error('error finding email');
} else {
if (item) {
if (item.password == password) {
console.log('logged in');
//callback(null, item);
//return item;
} else {
console.log('here');
throw new Error('Email and password not matching');
}
} else {
throw new Error('Email not found');
}
}
} catch (err) {
console.log('catch error here');
callback(err, null);
} finally {
console.log('finally here');
callback(null, item);
}
});
}
});
}
test/login-test.js
var expect = require('expect.js'),
assert = require('assert'),
mocha = require('mocha'),
mongo = require('mongodb');
var login = require('../login');
describe('login', function() {
it('should login a real user', function(done) {
expect(function() {
login.login(function(err, item) {
//console.log(item);
if (err) throw err;
done();
}, 'email', 'password')
}).to.not.throwError();
});
it('should error on unfound email', function(done) {
expect(function() {
login.login(function(err, item) {
console.log(err);
if (err) throw err;
done();
}, 'ert','wqew')
}).to.throwError();
});
it('should error on incorrect match', function(done) {
expect(function() {
login.login(function(err, item) {
console.log(err);
throw err;
done();
}, 'email','wqew')
}).to.throwError();
});
});
Old code
login.js
var MongoClient = require('mongodb').MongoClient;
exports.login = function(email, password, callback, errCallback) {
MongoClient.connect("mongodb://localhost:27017/stockalertDev", function(err, db) {
if (err) {
return err;
}
var collection = db.collection('users');
if (email) {
collection.findOne({email:email}, function(err, item) {
try {
if (err) {
console.log('error');
throw new Error('error finding email');
errCallback(err);
} else {
if (item) {
if (item.password == password) {
console.log('logged in');
callback(item);
//return item;
} else {
console.log('here');
throw new Error('Email and password not matching');
}
} else {
throw new Error('Email not found');
}
}
} catch (err) {
errCallback(err);
}
});
}
});
}
test/login-test.js
var expect = require('expect.js'),
assert = require('assert'),
mocha = require('mocha'),
mongo = require('mongodb');
var login = require('../login');
describe('login', function() {
it('should login a real user', function(done) {
assert.doesNotThrow(function() {
login.login('email','password',function() {
done();
}, function(err) {
if (err) throw err;
done();
});
});
});
it('should error on unfound email', function(done) {
expect( function() {
login.login('atreq','a', function() {
console.log('true');
}, function(err) {
console.log(err);
throw err;
})}).to.throwError();
});
it('should error on incorrect match', function(done) {
expect(function() {
login.login('email','apassword', function() {
console.log('true');
done();
}, function(err) {
console.log(err);
throw err;
})
}).to.throwError();
});
});
Using exeptions in asynchronous Node code is a bad idea (at least for the moment). There is a concept called Domains which could help you but is extremely experimental yet.
I suggest to do it the Node way: Reserve the first parameter of your callback for errors. Small example:
function getUserData(cb) {
var userData = // ...
if (userData === null) {
cb(new Error('Something bad happend.'));
} else {
cb(null, userData)
}
}
If you want to use a errorCallback as you already do use it:
errCallback(new Error('Email not found'));
Than you can do something like this (most testing frameworks provide helper methods for this, however I'm not that familar with Mocha and it's modules):
it('should login a real user', function(done) {
login.login(function(err, item) {
expect(err).to.be(null);
expect(item).not.to.be(null);
done();
}, 'email', 'password');
});
it('should error on unfound email', function(done) {
login.login(function(err, item) {
expect(err).not.to.be(null);
expect(item).to.be(null);
done();
}, 'ert','wqew');
});