如何单元测试一个需要其他模块.js模块,以及如何模拟全局需要函数?

2022-08-30 02:04:03

这是一个微不足道的例子,说明了我问题的症结所在:

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

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

module.exports = underTest;

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

所以这是我试图嘲笑全球,并发现即使这样做也行不通:require

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;
    }
};

问题是文件内的函数实际上没有被模拟出来。它仍然指向全局函数。因此,似乎我只能在我进行模拟的同一文件中模拟出函数。如果我使用全局包含任何内容,即使我覆盖了本地副本,所需的文件仍将具有全局引用。requireunderTest.jsrequirerequirerequirerequire


答案 1

您现在可以了!

我发布了 proxyquire,它将负责在测试模块时覆盖模块中的全局要求。

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

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

@Raynos是正确的,传统上你必须诉诸不是很理想的解决方案来实现这一点,或者进行自下而上的开发。

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

查看文档和示例,以衡量它是否适合您的需求。


答案 2

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

无论好坏,大多数节点.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
  });
});

Sinon与chai有很好的集成来做出断言,我写了一个模块将sinon与mocha集成在一起,以便更容易地清理间谍/存根(以避免测试污染)。

请注意,不能以相同的方式模拟 underTest,因为 underTest 仅返回一个函数。

另一种选择是使用Jest mocks。跟进他们的页面