在 Node.js 中模拟模块进行单元测试

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15790505/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 14:05:53  来源:igfitidea点击:

Mocking modules in Node.js for unit testing

node.jsunit-testingmockingjasmine

提问by Arne Jenssen

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

我想对 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

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.registerfunction but i don't want the test to hit the database. I may not have implemented the account module yet.

我希望能够测试在帖子中使用正确的正文点击此 url 会调用该account.register函数,但我不希望测试访问数据库。我可能还没有实现帐户模块。

The jasmine spec # specs/auth.test.coffee describe 'signup', ->

茉莉花规范#specs/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: '[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()

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.registeralways get executed. Is this whole approach flawed?

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

采纳答案by Stefan

I am using mochaas the test framework and sinonfor mocking, stubing and spying. I would suggest you delegate your account module to the auth.coffee module and mock it like so:

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

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.

因此,从 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();

    });

});

Sorry for not writing it down in coffescript but I am not used to it.

很抱歉没有用 coffescript 写下来,但我不习惯。

回答by Lautaro Ramos

I recommend proxyquire.

我推荐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.

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

回答by Arne Jenssen

Stefan's solution works. I just add some details.

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

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

and the routes/auth.coffeemodule ...

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

回答by George Ivanov

I've been using gentlyfor mocking and stubbing and mochafor test framework, and should.js for BDD style of tests. Here is how a sample unit test for me look like:

我一直温和地使用 mocking 和 stubbing,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');
    });
});