Mocking модули в Node.js для модульного тестирования
Я хочу unit test некоторые функции в модуле node.js. Я думаю, что насмешка над 3-м модулем будет полезна. В частности, чтобы избежать попадания в базу данных
# 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
Это модуль, который я хочу протестировать
# 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
Я хочу, чтобы проверить, что удар по этому URL-адресу с правильным телом в сообщении вызывает функцию account.register
, но я не хочу, чтобы тест попадал в базу данных. Возможно, я еще не реализовал модуль учетной записи.
Спектр жасмина # specs/auth.test.coffee описать '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: '[email protected]'
password: 'pw'
body = JSON.stringify(data)
request {url: url, method: 'POST',json: data, headers: headers }, (err, response, body)->
expect(response.statusCode).toEqual(200)
done()
Я просмотрел несколько издевательских библиотек для node.js(https://github.com/easternbloc/Syringe, https://github.com/felixge/node-sandboxed-module), но пока ничего не получилось. Независимо от того, что я пытаюсь использовать в спецификации, account.register
всегда выполняется. Недостаток этого подхода?
Ответы
Ответ 1
Я использую mocha в качестве рамки тестирования и sinon для насмешек, шпионаж. Я бы предложил вам делегировать ваш модуль учетной записи в модуль auth.coffee и высмеивать его так:
exports.init = function (account) {
// set account object
}
поэтому из теста мокки вы можете создать объект фиктивной учетной записи и высмеять его с помощью синона в фактическом тесте.
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();
});
});
Извините, что не записал его в coffescript, но я не привык к нему.
Ответ 2
Решение Стефана работает. Я просто добавляю некоторые подробности.
describe 'register', ->
account = response = routes_auth = null
beforeEach ->
account =
register: (email, pw, callback)->
if email is '[email protected]'
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: '[email protected]'
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()
и модуль routes/auth.coffee
...
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
Ответ 3
Я использовал gently для насмешек и stubbing и mocha для тестовой среды, и should.js для тестов BDD. Вот как выглядит образец unit test для меня:
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');
});
});
Ответ 4
Я рекомендую proxyquire.
Он делает то, что вы хотите достичь, не полагаясь на внедрение зависимостей, что навязчиво для вашего кода и требует изменений кода, если вы не писали свои модули таким образом.