Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/node.js/42.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 如何对需要其他模块的Node.js模块进行单元测试,以及如何模拟global require函数?_Javascript_Node.js_Mocking - Fatal编程技术网

Javascript 如何对需要其他模块的Node.js模块进行单元测试,以及如何模拟global require函数?

Javascript 如何对需要其他模块的Node.js模块进行单元测试,以及如何模拟global require函数?,javascript,node.js,mocking,Javascript,Node.js,Mocking,这是一个简单的例子,说明了问题的症结: var innerLib = require('./path/to/innerLib'); function underTest() { return innerLib.doComplexStuff(); } module.exports = underTest; 我正试图为这段代码编写一个单元测试。如何模拟innerLib的需求,而不完全模拟require函数 因此,这是我试图模拟全局require,发现这样做根本不起作用: var pat

这是一个简单的例子,说明了问题的症结:

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

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

module.exports = underTest;
我正试图为这段代码编写一个单元测试。如何模拟
innerLib
的需求,而不完全模拟
require
函数

因此,这是我试图模拟全局
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;
    }
};

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

您不能。您必须构建单元测试套件,以便首先测试最低级别的模块,然后测试需要模块的较高级别的模块

您还必须假设任何第三方代码和node.js本身都经过良好测试

我想在不久的将来,您会看到覆盖
global.require的模拟框架出现

如果您真的必须注入一个mock,那么您可以更改代码以公开模块范围

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

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

module.exports = underTest;
module.exports.__module = module;

// test.js
function test() {
    var underTest = require("underTest");
    underTest.__module.innerLib = {
        toCrazyCrap: function() { return true; }
    };
    assert.ok(underTest());
}
请注意,这会将
\u模块
暴露到您的API中,任何代码都可以访问模块作用域,这会带来危险。

您现在可以

我发表了一篇文章,它将在您测试模块时覆盖模块内部的全局需求

这意味着您不需要对代码进行任何更改,以便为所需的模块注入模拟

Proxyquire有一个非常简单的api,它允许在一个简单的步骤中解析您试图测试的模块,并传递所需模块的mock/stub

@Raynos是对的,传统上,为了实现这一点,您必须求助于不太理想的解决方案,或者进行自下而上的开发

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


查看文档和示例,以确定它是否适合您的需要。

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

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

您可以利用这一点,并使用类似的方法模拟所需的项目。测试如下:

// 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很擅长断言,我编写了一个模块来简化间谍/存根清理(以避免测试污染)

请注意,不能以相同的方式模拟欠测试,因为欠测试只返回一个函数

另一种选择是使用Jest Mock。跟进我使用的

。确保在要求测试模块之前定义模拟。
您可以使用库:


嘲笑
require
对我来说就像是一种讨厌的攻击。我个人会尽量避免它,并重构代码,使其更易于测试。 处理依赖关系有多种方法

1) 将依赖项作为参数传递

function underTest(innerLib) {
    return innerLib.doComplexStuff();
}
这将使代码具有普遍可测试性。缺点是需要传递依赖项,这会使代码看起来更复杂

2) 将模块作为类实现,然后使用类方法/属性获取依赖项

(这是一个人为的例子,其中类的使用是不合理的,但它传达了这个想法) (ES6示例)

现在,您可以轻松地使用stub
getInnerLib
方法测试代码。 代码变得更加冗长,但也更易于测试。

为好奇的用户模拟模块的简单代码 注意操作
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 = {}
  }
}
使用like

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

但是。。。proxyquire非常棒,你应该使用它。它使您的require覆盖仅局限于测试,我强烈建议您这样做。

如果您曾经使用过jest,那么您可能熟悉jest的mock特性

使用“jest.mock(…)”可以简单地指定代码中某个地方require语句中出现的字符串,并且每当需要使用该字符串的模块时,将返回一个mock对象

比如说

jest.mock("firebase-admin", () => {
    const a = require("mocked-version-of-firebase-admin");
    a.someAdditionalMockedMethod = () => {}
    return a;
})
将使用从“工厂”函数返回的对象完全替换“firebase admin”的所有导入/要求

在使用jest时,您可以这样做,因为jest围绕它运行的每个模块创建一个运行时,并将“钩住”版本的require注入到模块中,但是如果没有jest,您将无法做到这一点

我曾试图用实现这一点,但对我来说,它不适用于源代码中的嵌套级别。请查看github上的以下问题:

为了解决这个问题,我创建了两个npm模块,您可以使用它们来实现您想要的

您需要一个babel插件和一个模块模拟程序

在.babelrc中,使用带有以下选项的babel插件mock require插件:

...
"plugins": [
        ["babel-plugin-mock-require", { "moduleMocker": "jestlike-mock" }],
        ...
]
...
在测试文件中使用jestlike模拟模块,如下所示:

import {jestMocker} from "jestlike-mock";
...
jestMocker.mock("firebase-admin", () => {
            const firebase = new (require("firebase-mock").MockFirebaseSdk)();
            ...
            return firebase;
});
...
jestlikemock
模块仍然非常初级,没有太多文档,但也没有太多代码。我感谢任何PRs提供更完整的功能集。目标是重新创建整个“jest.mock”功能

为了了解jest是如何实现的,可以在“jest运行时”包中查找代码。例如,请参见此处,它们生成模块的“自动模拟”


希望有帮助;)

您必须覆盖
全局。需要
。变量写入
模块
...
"plugins": [
        ["babel-plugin-mock-require", { "moduleMocker": "jestlike-mock" }],
        ...
]
...
import {jestMocker} from "jestlike-mock";
...
jestMocker.mock("firebase-admin", () => {
            const firebase = new (require("firebase-mock").MockFirebaseSdk)();
            ...
            return firebase;
});
...