如何对需要其他模块的node.js模块进行单元测试

Mat*_*lor 149 javascript mocking node.js

这是一个简单的例子,说明了我的问题的关键:

var innerLib = require('./path/to/innerLib');

function underTest() {
    return innerLib.doComplexStuff();
}

module.exports = underTest;
Run Code Online (Sandbox Code Playgroud)

我正在尝试为此代码编写单元测试.如何在innerLibrequire完全嘲笑函数的情况下模拟出对该函数的要求?

编辑:所以这是我试图模拟全局需求,并发现它甚至不会这样做:

var path = require('path'),
    vm = require('vm'),
    fs = require('fs'),
    indexPath = path.join(__dirname, './underTest');

var globalRequire = require;

require = function(name) {
    console.log('require: ' + name);
    switch(name) {
        case 'connect':
        case indexPath:
            return globalRequire(name);
            break;
    }
};
Run Code Online (Sandbox Code Playgroud)

问题是requireunderTest.js文件中的函数实际上没有被模拟出来.它仍然指向全球require功能.所以我似乎只能underTest.js在同一个文件中模拟我正在进行模拟的函数.如果我使用全局require来包含任何内容,即使在我重写本地副本之后,所需的文件仍将具有全局require引用.

Tho*_*enz 170

你现在可以!

我发布了proxyquire,它将在您测试时覆盖模块内部的全局需求.

这意味着您无需更改代码即可为所需模块注入模拟.

Proxyquire有一个非常简单的api,它允许解析你试图测试的模块,并通过一个简单的步骤传递其所需模块的模拟/存根.

@Raynos是对的,传统上你不得不诉诸不是非常理想的解决方案来实现这一目标或者做自下而上的开发

这是我创建proxyquire的主要原因 - 允许自上而下的测试驱动开发没有任何麻烦.

查看文档和示例,以确定它是否符合您的需求.

  • 我使用proxyquire,我不能说足够好的东西.它救了我!我的任务是为在appcelerator Titanium中开发的应用程序编写茉莉花节点测试,这会强制某些模块成为绝对路径和许多循环依赖.proxyquire让我阻止那些并模拟我每次测试都不需要的瑕疵.(解释[此处](http://stackoverflow.com/questions/14669390/how-do-you-force-the-absolute-path-for-node-modules/14705870#14705870)).谢谢你太多了! (5认同)
  • 对于那些使用Webpack的人,不要花时间研究proxyquire.它不支持Webpack.我正在研究注入加载器(https://github.com/plasticine/inject-loader). (3认同)

Ell*_*ter 112

在这种情况下,更好的选择是模拟返回的模块的方法.

无论好坏,大多数node.js模块都是单例; 需要()相同模块的两段代码获得与该模块相同的引用.

你可以利用这个并使用像sinon这样的东西来模拟所需的项目. 摩卡测试如下:

// in your testfile
var innerLib  = require('./path/to/innerLib');
var underTest = require('./path/to/underTest');
var sinon     = require('sinon');

describe("underTest", function() {
  it("does something", function() {
    sinon.stub(innerLib, 'toCrazyCrap').callsFake(function() {
      // whatever you would like innerLib.toCrazyCrap to do under test
    });

    underTest();

    sinon.assert.calledOnce(innerLib.toCrazyCrap); // sinon assertion

    innerLib.toCrazyCrap.restore(); // restore original functionality
  });
});
Run Code Online (Sandbox Code Playgroud)

Sinon 与chai进行了良好的集成以进行断言,我编写了一个模块来将sinon与mocha集成,以便更容易地进行间谍/存根清理(以避免测试污染.)

请注意,underTest不能以相同的方式进行模拟,因为underTest只返回一个函数.

  • 不幸的是,node.js 模块不能保证是单例,如下所述:http://justjs.com/posts/singletons-in-node-js-modules-cannot-be-trusted-or-why-you-can -t-just-do-var-foo-require-baz-init (3认同)
  • @FrontierPsycho有几件事:首先,就测试而言,文章是无关紧要的.只要您测试依赖项(而不是依赖项的依赖项),当您需要('some_module')`时,所有代码都会返回相同的对象,因为所有代码都共享相同的node_modules目录.其次,文章将命名空间与单例混合,这是一种正交.第三,那篇文章相当古老(就node.js而言),因此当天可能有效的东西现在可能无效. (3认同)
  • 嗯.除非我们中的一个人真正挖掘出证明某一点或另一个点的代码,否则我会选择依赖注入的解决方案,或者只是简单地传递对象,它更安全,更具未来性. (2认同)

Kun*_*nal 11

我用mock-require.确保require在测试模块之前定义模拟.


Jas*_*ing 5

为好奇的人模拟模块的简单代码

请注意操作require.cache并注意require.resolve方法的部分,因为这是秘密武器。

class MockModules {  
  constructor() {
    this._resolvedPaths = {} 
  }
  add({ path, mock }) {
    const resolvedPath = require.resolve(path)
    this._resolvedPaths[resolvedPath] = true
    require.cache[resolvedPath] = {
      id: resolvedPath,
      file: resolvedPath,
      loaded: true,
      exports: mock
    }
  }
  clear(path) {
    const resolvedPath = require.resolve(path)
    delete this._resolvedPaths[resolvedPath]
    delete require.cache[resolvedPath]
  }
  clearAll() {
    Object.keys(this._resolvedPaths).forEach(resolvedPath =>
      delete require.cache[resolvedPath]
    )
    this._resolvedPaths = {}
  }
}
Run Code Online (Sandbox Code Playgroud)

使用如下

describe('#someModuleUsingTheThing', () => {
  const mockModules = new MockModules()
  beforeAll(() => {
    mockModules.add({
      // use the same require path as you normally would
      path: '../theThing',
      // mock return an object with "theThingMethod"
      mock: {
        theThingMethod: () => true
      }
    })
  })
  afterAll(() => {
    mockModules.clearAll()
  })
  it('should do the thing', async () => {
    const someModuleUsingTheThing = require('./someModuleUsingTheThing')
    expect(someModuleUsingTheThing.theThingMethod()).to.equal(true)
  })
})
Run Code Online (Sandbox Code Playgroud)

但是... jest 内置了此功能,我建议您使用测试框架来进行测试。