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

This is a trivial example that illustrates the crux of my problem:

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

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

module.exports = underTest;

I am trying to write a unit test for this code. How can I mock out the requirement for the innerLib without mocking out the require function entirely?

So this is me trying to mock out the global require and finding out that it won’t work even to do that:

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

The problem is that the require function inside the underTest.js file has actually not been mocked out. It still points to the global require function. So it seems that I can only mock out the require function within the same file I’m doing the mocking in. If I use the global require to include anything, even after I’ve overridden the local copy, the files being required will still have the global require reference.

小小2020/03/24 19:10:40

如果您曾经使用过jest,那么您可能对jest的模拟功能很熟悉。

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

例如

jest.mock("firebase-admin", () => {
    const a = require("mocked-version-of-firebase-admin");
    a.someAdditionalMockedMethod = () => {}
    return a;
})

会用您从该“工厂”功能返回的对象完全替换“ firebase-admin”的所有导入/要求。

好吧,您可以在使用jest时执行此操作,因为jest会在它运行的每个模块周围创建一个运行时,并将“挂钩”版本的require注入到模块中,但是如果没有jest,您将无法执行此操作。

我试图通过模拟需求来实现这一点,但对我而言,它不适用于源代码中的嵌套级别。看看github上的以下问题:并非总是用Mocha调用mock-require

为了解决这个问题,我创建了两个npm模块,您可以使用它们来实现所需的功能。

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

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

...
"plugins": [
        ["babel-plugin-mock-require", { "moduleMocker": "jestlike-mock" }],
        ...
]
...

并在测试文件中使用jestlike-mock模块,如下所示:

import {jestMocker} from "jestlike-mock";
...
jestMocker.mock("firebase-admin", () => {
            const firebase = new (require("firebase-mock").MockFirebaseSdk)();
            ...
            return firebase;
});
...

jestlike-mock模块仍然非常初级,并且没有很多文档,但是也没有太多代码。我感谢任何PR提供了更完整的功能集。目标是重新创建整个“ jest.mock”功能。

为了了解jest如何实现,可以在“ jest-runtime”包中查找代码。例如,请参见https://github.com/facebook/jest/blob/master/packages/jest-runtime/src/index.js#L734,此处它们生成模块的“自动模拟”。

希望能有所帮助;)

小胖Mandy2020/03/24 19:10:40

你现在可以!

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

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

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

@Raynos是正确的,传统上,您必须诉诸不太理想的解决方案才能实现该目标或进行自下而上的开发

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

请查看文档和示例,以判断它是否适合您的需求。

古一2020/03/24 19:10:40

我使用模拟需求确保require在测试模块之前定义了模拟。