使用 webpack 的 require.ensure 函数为 javascript 模块编写测试

Writing tests for javascript module using webpack's require.ensure function

我正在 运行我的服务器上进行 mocha 测试,以独立的单元测试方式测试源脚本。

我正在测试的脚本之一会调用 Webpack 的 require.ensure 函数,这对于在应用程序被 Webpack 捆绑时创建代码拆分点很有用。

我为此脚本编写的测试不在 Webpack 上下文中 运行,因此 require.ensure 函数不存在,测试失败。

我曾尝试为此功能创建某种 polyfill/stub/mock/spy,但没有成功。

有一个包 webpack-require,它允许创建 webpack 上下文。这可以工作,但速度慢得令人无法接受。我更希望有某种直接针对 require.ensure 函数的轻量级 polyfill。

有什么建议吗? :)


这是一个非常基本的起点 mocha 测试。

mocha 测试加载一个人为设计的模块,其中包含一个方法,如果定义了 require.ensure,则该方法 returns 为真。

foo.js

export default {
  requireEnsureExists: () => {
    return typeof require.ensure === 'function';
  }
};

foo.test.js

import { expect } from 'chai';

describe('When requiring "foo"', () => {
  let foo;

  before(() => {
    foo = require('./foo.js');
  });

  it('The requireEnsureExists() should be true', () => {
    expect(foo.requireEnsureExists()).to.be.true;
  });
});

好的,经过大量的研究和思考,我终于有了一个答案。

我最初认为我可以使用某种 IoC / DI 策略来解决这个问题,但后来我发现了 source code for Node JS's Module library which is responsible for loading modules. Looking at the source code you will notice that the 'require' function for modules (i.e. foo.js in my example) get created by the _compile function of NodeJs's module loader。它是内部范围的,我看不到修改它的直接机制。

我不太确定 Webpack 如何或在何处扩展创建的 "require" 实例,但我怀疑它有一些黑魔法。我意识到我需要一些帮助来做类似性质的事情,并且不想为此编写大量复杂的代码。

然后我无意中发现了 rewire...

Dependency injection for node.js applications.

rewire adds a special setter and getter to modules so you can modify their behaviour for better unit testing. You may

  • inject mocks for other modules
  • leak private variables
  • override variables within the module.
  • rewire does not load the file and eval the contents to emulate node's require mechanism. In fact it uses node's own require to load the module. Thus your module behaves exactly the same in your test environment as under regular circumstances (except your modifications).

完美。我只需要访问私有变量。

安装 rewire 后,让我的测试工作很容易:

foo.js

export default {
  requireEnsureExists: () => {
    return typeof require.ensure === 'function';
  }
};

foo.test.js

import { expect } from 'chai';
import rewire from 'rewire';

describe('When requiring "foo"', () => {
  let foo;

  before(() => {
    foo = rewire('./foo.js');

    // Get the existing 'require' instance for our module.
    let fooRequire = moduletest.__get__('require');

    // Add an 'ensure' property to it.
    fooRequire.ensure = (path) => {
      // Do mocky/stubby stuff here.
    };

    // We don't need to set the 'require' again in our module, as the above
    // is by reference.
  });

  it('The requireEnsureExists() should be true', () => {
    expect(foo.requireEnsureExists()).to.be.true;
  });
});

啊啊啊啊....太开心了。快速运行再次测试土地。

哦,在我的情况下不需要,但是如果您通过 webpack 捆绑代码以进行基于浏览器的测试,那么您可能需要 rewire-webpack 插件。我还在某处读到这可能与 ES6 语法有问题。

另一个注意事项:对于 require(...) 语句的直接模拟,我建议使用 mockery 而不是 rewire。它不如 rewire 强大(没有私有变量访问),但在我看来这更安全一些。此外,它还有一个非常有用的警告系统,可帮助您避免进行任何无意的模拟。


更新

我还看到正在采用以下策略。在每个使用 require.ensure 的模块中检查它是否存在,如果不存在则填充它:

// Polyfill webpack require.ensure.
if (typeof require.ensure !== `function`) require.ensure = (d, c) => c(require);