【问题标题】:Mocking modules in Node.js for unit testing在 Node.js 中模拟模块以进行单元测试
【发布时间】:2013-03-25 07:19:43
【问题描述】:

我想对 node.js 模块中的一些函数进行单元测试。我认为模拟第三个模块会有所帮助。特别是为了避免撞到数据库

# 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 函数,但我不希望测试访问数据库。我可能还没有实现 account 模块。

茉莉花规格 # 规格/auth.test.coffee 描述“注册”,->

   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()

我已经研究了几个 node.js 的模拟库(https://github.com/easternbloc/Syringe,https://github.com/felixge/node-sandboxed-module),但到目前为止没有成功。无论我在规范中尝试什么,account.register 总是会被执行。这整个方法有缺陷吗?

【问题讨论】:

  • 看起来 horaa (github.com/arunoda/horaa) 可能有用
  • 试试 sinon.js,一个优秀的 stub/spy/mock/mockServer 库。

标签: node.js unit-testing mocking jasmine


【解决方案1】:

我使用mocha 作为测试框架,sinon 用于模拟、存根和间谍活动。我建议您将您的帐户模块委托给 auth.coffee 模块并像这样模拟它:

exports.init = function (account) {
    // set account object
}

因此,您可以从 mocha 测试中创建一个虚拟帐户对象,并在实际测试中使用 sinon 模拟它。

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();

    });

});

很抱歉没有把它写在咖啡稿里,但我不习惯。

【讨论】:

  • 谢谢。这行得通。这种方法不是劫持模块,而是进行依赖注入和模拟。
  • 你不需要在beforeEach方法中实现account.register。只需像我调用yields 一样使用存根来进行异步回调。查看 sinon 文档中的存根和产量。
【解决方案2】:

Stefan 的解决方案有效。我只是添加一些细节。

    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() 

还有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 进行模拟和存根,mocha 用于测试框架,而 should.js 用于 BDD 风格的测试。这是我的示例单元测试的样子:

    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。

      它可以实现您想要实现的目标,同时不依赖于依赖注入,这对您的代码来说很突兀,并且如果您没有以这种方式编写模块,则需要更改代码。

      【讨论】:

      • 谢谢,这就是我要找的!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-04-06
      • 1970-01-01
      • 2018-09-04
      • 2013-11-12
      • 2018-11-21
      • 2018-10-19
      • 2013-07-24
      相关资源
      最近更新 更多