如何使用 ES6 模块模拟单元测试的依赖关系

Posted

技术标签:

【中文标题】如何使用 ES6 模块模拟单元测试的依赖关系【英文标题】:How to mock dependencies for unit tests with ES6 Modules 【发布时间】:2015-02-04 01:04:26 【问题描述】:

我正在尝试使用 webpack + traceur 处理 Ecmascript 6 模块以转换为 ES5 CommonJS,但我无法成功对它们进行单元测试。

我尝试使用 Jest + traceur 预处理器,但 automocking 和依赖项名称似乎变得古怪,而且我似乎无法让 sourceMaps 与 Jest 和节点检查器调试一起工作。

是否有更好的框架来对 ES6 模块进行单元测试?

【问题讨论】:

我不知道 Traceur,但 6to5 很适合 jest 并且有一个 6to5-jest plugin。 谢谢詹姆斯,我会去看看。它看起来确实像 6to5 转换成更易读的并且涵盖了我需要的所有 ES6 功能,所以也许我会走这条路。 另外,你能用 node-inspector 调试得到 sourceMaps 吗? 我还推荐使用 Babel.js 将你的 ES6 代码转换成 ES5。在编译测试或实现代码时,我没有遇到任何问题。 babeljs.io 如果您使用的是 babel,那么您也可以简单地使用 babel-plugin-rewire 模拟模块,而不是使用专用的 webpack 或 browserify 加载器。 【参考方案1】:

我已经开始在我的测试中使用import * as obj 样式,它将模块中的所有导出作为对象的属性导入,然后可以对其进行模拟。我发现这比使用 rewire 或 proxyquire 或任何类似技术要干净得多。

我不能代表问题中使用的框架 traceur,但我发现这适用于我的 Karma、Jasmine 和 Babel 设置,我将其发布在这里,因为这似乎是这种类型的最受欢迎的问题。

当需要模拟 Redux 操作时,我最常使用此策略。这是一个简短的例子:

import * as exports from 'module-you-want-to-mock';
import SystemUnderTest from 'module-that-uses-above-module';

describe('your module', () => 
  beforeEach(() => 
    spyOn(exports, 'someNamedExport');  // mock a named export
    spyOn(exports, 'default');          // mock the default export
  );
  // ... now the above functions are mocked
);

【讨论】:

这种模式或技术应该被更好地记录为模拟 es6 模块而不是依赖第三方依赖项的有用方法。我很想看到随着 es6 在单元测试方面变得更加主流,这种模式在导入模块的示例中更常见,而不是使用 import someExport from module 表示法。 既然你提到了 redux 动作,值得指出的是,当动作绑定到连接的组件时,必须密切注意。将间谍应用到已经绑定的函数可能没有效果。了解调用 react-redux 的 connect() 函数的不同方法是我在不失去理智的情况下让它工作的关键:github.com/reactjs/react-redux/blob/master/docs/… 如何防止测试相互渗透?根据我的经验,模拟导入会导致以后的测试使用模拟而不是真实实例,反之亦然。 @user831865 根据您的测试框架,模拟的范围应该是模拟定义的describe 块。如果不了解您的特定设置,很难说。 @user831865 如果你正在使用 sinon,sinon.sandbox 可以防止你的模拟流到你的其他测试中。【参考方案2】:

如果您使用 Webpack,另一个比 rewire 更灵活的选项是 inject-loader。

例如,在与 Webpack 捆绑的测试中:

describe('when an alert is dismissed', () => 

  // Override Alert as we need to mock dependencies for these tests
  let Alert, mockPubSub

  beforeEach(() => 
    mockPubSub = 
    Alert =  require('inject!./alert')(
      'pubsub-js': mockPubSub
    ).Alert
  )

  it('should publish \'app.clearalerts\'', () => 
    mockPubSub.publish = jasmine.createSpy()
    [...]
    expect(mockPubSub.publish).toHaveBeenCalled()
  )
)

inject-loader,以与 proxyquire 类似的方式,至少允许在导入之前注入依赖项,而在 rewire 中,您必须先导入然后 rewire,这使得模拟某些组件(例如,那些具有某些初始化的组件)变得不可能。

【讨论】:

是的,但这仅在您使用require 而不是import .. as ..syntax 时有效 不正确。我成功地将它与 ES6 导入语法一起使用。我想这取决于您的设置,但我将 Babel 与 babel-loader 一起使用。它适用于 require 和 ES6 导入。唯一需要 require 的地方是在测试本身中模拟依赖项。 嗨@djskinner。我正在尝试让注入加载器与 babel、webpack 和 karam 一起使用,但遇到了麻烦。您介意将您的karma.config.js(和webpack.config 文件,如果您使用的话)添加到您的答案中。提前致谢。 查看this answer 以获得良好的起点。它主要是关于代码覆盖率,但即使您对代码覆盖率部分不感兴趣,基本配置也很好。至于inject-loader,我不需要任何额外的配置,只需npm i inject-loaderrequire(inject!...) 如何将其与 amd 模块一起使用?我得到“模块名称“inject!src/qux_unnormalized2”尚未加载上下文”【参考方案3】:

您好,您可以使用 proxyquire:

import assert from 'assert';
import sinon from 'sinon';
import Proxyquire from 'proxyquire';

let proxyquire = Proxyquire.noCallThru(),
    pathModelLoader = './model_loader';

describe('ModelLoader module.', () => 
    it('Should load all models.', () => 
        let fs, modelLoader, ModelLoader, spy, path;
        fs = 
            readdirSync(path) 
                return ['user.js'];
            
        ;
        path = 
            parse(data) 
                return name: 'user';
            
        ;
        ModelLoader = proxyquire(pathModelLoader, 'fs': fs, 'path': path);
        modelLoader = new ModelLoader.default();
        spy = sinon.spy(modelLoader, 'loadModels');
        modelLoader.loadModels();
        assert(spy.called);
    );
);

【讨论】:

【参考方案4】:

我实际上是通过放弃 Jest 并使用 Karma + Jasmine + Webpack 并使用 https://github.com/jhnns/rewire 来模拟依赖项来实现这一点

【讨论】:

函数内部的变量不能通过rewire来改变。这受 javascript 限制,无法通过重新布线来规避。 我很高兴这个建议对你有用......但它是如何被接受的答案? “不要使用那个”不一定有帮助:(【参考方案5】:

Proxyquire 会帮助你,但它不适用于现代 webpack+ES6 模块,即“别名”。

import fs from 'fs';
import reducers from 'core/reducers';
...
proxyquire('../module1', 
  'fs': mockFs,  // this gonna work
  'core/reducers': mockReducers // what about this?
);

that 将不起作用。 只要你可以模拟 fs - 你就不能模拟减速器。 在任何 webpack 或 babel 转换之后,您必须指定依赖项的 real 名称。通常 - 相对于 module1 位置的名称。可能是“../../../shared/core/reducers”。可能不是。

有一些解决方案 - https://github.com/theKashey/proxyquire-webpack-alias(稳定,基于 proxyquire 的分支)或 https://github.com/theKashey/resolveQuire(不太稳定,可以在原始 proxyquire 上运行)

它们都可以正常工作,并且会以代理查询方式模拟任何 ES6 模块(它们非常好)(这是一个好方法)

【讨论】:

以上是关于如何使用 ES6 模块模拟单元测试的依赖关系的主要内容,如果未能解决你的问题,请参考以下文章

Jest Manual Mocks with React 和 Typescript:模拟 ES6 类依赖

在 Jasmine 单元测试中模拟 AngularJS 模块依赖项

在 AngularJS 单元测试中模拟模块依赖

在AngularJS单元测试中模拟模块依赖性

如何测试依赖于具有关系的 Eloquent 模型的类?

具有javascript依赖关系的lit-element,未打包为es6模块(尚未)