I want to unit test some functions in a node.js module. I think that mocking a 3rd module would be helpful. In particular to avoid hitting the database
# models/account.coffee
register = (email, password)->
sha_sum.update(password)
pw = sha_sum.digest('hex')
user =
email: email
password: sha_sum.digest('hex')
users_db.save user, (err, doc)->
register_callback(err)
account_module =
register: register
module.exports = account_module
This is the module that i want to test
# routes/auth.coffee
account = require '../models/account'
exports.auth =
post_signup: (req, res)->
email = req.body.email
password = req.body.password
if email and password
account.register(email, password)
res.send 200
else
res.send 400
I want to be able to test that hitting this url with the correct body in the post calls the account.register
function but i don't want the test to hit the database. I may not have implemented the account module yet.
The jasmine spec # specs/auth.test.coffee describe 'signup', ->
request = require 'request'
it 'should signup a user with username and password', (done)->
spyOn(account, 'register') # this does not work, account.register still called
url = root + '/signup'
headers =
"Content-Type": "application/json"
data =
email: 'user@email.com'
password: 'pw'
body = JSON.stringify(data)
request {url: url, method: 'POST',json: data, headers: headers }, (err, response, body)->
expect(response.statusCode).toEqual(200)
done()
I have looked into several mocking libraries for node.js ( https://github.com/easternbloc/Syringe , https://github.com/felixge/node-sandboxed-module ) but so far no success. Whatever i try in the spec, the account.register
always get executed. Is this whole approach flawed?
I am using mocha as the test framework and sinon for mocking, stubing and spying. I would suggest you delegate your account module to the auth.coffee module and mock it like so:
exports.init = function (account) {
// set account object
}
so from the mocha test you can then create a dummy account object and mock it with sinon in the actual test.
describe('some tests', function () {
var account, response, testObject;
beforeEach(function () {
account = {
register: function () { }
};
response = {
send: function () { }
};
testObject = require('./auth');
testObject.init(account);
});
it('should test something', function () {
var req = { body: { email: ..., password: .... } }, // the request to test
resMock = sinon.mock(response),
registerStub = sinon.stub(account, 'register');
// the request expectations
resMock.expect('send').once().withArgs(200);
// the stub for the register method to have some process
registerStub.once().withArgs('someargs');
testObject.auth(req. response);
resMock.verify();
});
});
Sorry for not writing it down in coffescript but I am not used to it.
Stefan's solution works. I just add some details.
describe 'register', ->
account = response = routes_auth = null
beforeEach ->
account =
register: (email, pw, callback)->
if email is 'valid@email.com'
callback(null, 1)
else
err = 'error'
callback(err, 0)
response =
send: -> {}
routes_auth = require('../routes/auth').init(account)
it 'should register a user with email and pw', (done)->
req =
body:
email: 'valid@email.com'
password: 'pw'
resMock = sinon.mock(response)
resMock.expects('send').once().withArgs(200)
routes_auth.post_register(req, response)
resMock.verify()
done()
it 'should not register a user without email', ()->
req =
body:
password: 'pw'
resMock = sinon.mock(response)
resMock.expects('send').once().withArgs(400)
routes_auth.post_register(req, response)
resMock.verify()
and the routes/auth.coffee
module ...
exports.init = (account)->
get_available: (req, res)->
email = req.param.email
if not email? or email.length < 1
res.send 400
return
account.available email, (err, doc)->
console.log 'get_available', err, doc
if err then res.send 401
else res.send 200
post_register: (req, res)->
email = req.body.email
password = req.body.password
if email and password
account.register email, password, (err, doc)->
if err then res.send 401
else res.send 200
else
res.send 400
I've been using gently for mocking and stubbing and mocha for test framework, and should.js for BDD style of tests. Here is how a sample unit test for me look like:
describe('#Store() ', function () {
it('will delegate the store to the CacheItem and CacheKey', function () {
var actualCacheKey, actualConnMgr, actualConfig, actualLogger, actualRequest;
var actualKeyRequest, actualKeyConfig;
gently.expect(
CacheKey, 'CreateInstance', function (apiRequest, config) {
actualKeyRequest = apiRequest;
actualKeyConfig = config;
return mockCacheKey;
});
gently.expect(
CacheItem, 'CreateInstance', function (cacheKey, connectionManager, config, logger, apiRequest) {
actualCacheKey = cacheKey;
actualConnMgr = connectionManager;
actualConfig = config;
actualLogger = logger;
actualRequest = apiRequest;
return mockCacheItem;
});
var actualApiRequest, actualCallback;
gently.expect(mockCacheItem, 'Store', function (request, callback) {
actualApiRequest = request;
actualCallback = callback;
});
var callback = function () {};
var apiResponse = {'item': 'this is a sample response from SAS'};
Cache.GetInstance(connMgr, config, logger).Store(apiRequest, apiResponse, callback);
mockCacheKey.should.be.equal(actualCacheKey, 'The cachkeKey to CacheItem.CreateIntsance() did not match');
connMgr.should.be.equal(
actualConnMgr, 'The connection manager to CacheItem.CreateInstance() did not match');
config.should.be.equal(actualConfig, 'The config to CacheItem.CreateInstance() did not match');
logger.should.be.equal(actualLogger, 'The logger to CacheItem.Createinstance did not match');
apiRequest.should.be.equal(actualRequest, 'The request to CacheItem.Createinstance() did not match');
apiRequest.should.be.equal(actualKeyRequest, 'The request to CacheKey.CreateInstance() did not match');
config.should.be.equal(actualKeyConfig, 'The config to CacheKey.CreateInstance() did not match');
callback.should.be.equal(actualCallback, 'The callback passed to CacheItem.Store() did not match');
apiResponse.should.be.equal(actualApiRequest, 'The apiRequest passed to CacheItem.Store() did not match');
});
});
I recommend proxyquire .
It does what you want to achieve, while not relying on dependency injection, which is obtrusive for your code and requires code changes if you didn't write your modules in that fashion.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.