【问题标题】:How to stub exported function in ES6?如何在 ES6 中存根导出的函数?
【发布时间】:2016-04-07 03:45:33
【问题描述】:

我有文件 foo.js:

export function bar (m) {
  console.log(m);
}

还有另一个使用 foo.js 的文件 cap.js:

import { bar } from 'foo';

export default m => {
  // Some logic that I need to test
  bar(m);
}

我有 test.js:

import cap from 'cap'

describe('cap', () => {
  it('should bar', () => {
      cap('some');
  });
});

不知何故,我需要在测试中覆盖 bar(m) 的实现。有没有办法做到这一点?

附:我使用 babel、webpack 和 mocha。

【问题讨论】:

  • 您需要在哪里覆盖上限或测试?
  • 在测试中,实际上我需要这个来将 cap 功能与 bar 隔离开来进行测试。
  • 你用的是什么版本的 babel?
  • @DavinTryon 6.3.15,如果重要的话,我实际上可以使用任何。
  • 如果你使用 babel 5,你可以使用这个插件:github.com/speedskater/babel-plugin-rewire。但它还不支持 babel 6。

标签: javascript ecmascript-6


【解决方案1】:

哎呀.. 我找到了解决方案,所以我使用sinon 存根和import * as foo from 'foo' 获取所有导出函数的对象,以便我可以存根它们。

import sinon from 'sinon';
import cap from 'cap';
import * as foo from 'foo';

sinon.stub(foo, 'bar', m => {
    console.log('confirm', m);
});

describe('cap', () => {
  it('should bar', () => {
    cap('some');
  });
});

【讨论】:

  • 我认为这不会在实际(非 babel 转译)ES6 环境中工作
  • @Bergi 是的,根据我目前的理解,导出实际上是导出不可变对象,并且因为现在这是使用常规对象实现的,所以以后可能会失败。
  • 那么解决这个问题的方法是什么?
  • 这不像@MikeChaliy 提到的那样工作。还有其他想法吗?
  • 由于 DreamSonic 引用的原因,这不起作用。
【解决方案2】:

您只能从模块本身内部替换/重写/存根导出。 (这里是explanation

如果你像这样重写 'foo.js':

var bar = function bar (m) {
  console.log(m);
};

export {bar}

export function stub($stub) {
  bar = $stub;
}

然后您可以像这样在测试中覆盖它:

import cap from 'cap'
import {stub} from 'foo'

describe('cap', () => {
  it('should bar', () => {
      stub(() => console.log('stubbed'));
      cap('some'); // will output 'stubbed' in the console instead of 'some'
  });
});

我创建了一个 Babel 插件,它可以自动转换所有导出,以便它们可以被存根:https://github.com/asapach/babel-plugin-rewire-exports

【讨论】:

    【解决方案3】:

    虽然@Mike 解决方案适用于旧版本的 sinon,但自 sinon 3.0.0 起已将其删除。

    现在代替:

    sinon.stub(obj, "meth", fn);
    

    你应该这样做:

    stub(obj, 'meth').callsFake(fn)
    

    模拟 google oauth api 示例:

    import google from 'googleapis';
    
    const oauth2Stub = sinon.stub(); 
    
    sinon.stub(google, 'oauth2').callsFake(oauth2Stub);
    
    oauth2Stub.withArgs('v2').returns({
        tokeninfo: (accessToken, params, callback) => {
            callback(null, { email: 'poo@bar.com' }); // callback with expected result
        }
    });
    

    【讨论】:

    • 我不认为这解决了关于在 sinon 3.x 中伪造导出函数的原始问题。你能扩展你的答案来解决原来的问题吗?
    【解决方案4】:

    您可以使用babel-plugin-rewire (npm install --save-dev babel-plugin-rewire)

    然后在test.js 中使用导入模块上的__Rewire__ 函数来替换该模块中的函数:

    // test.js
    import sinon from 'sinon'
    
    import cap from 'cap'
    
    describe('cap', () => {
      it('should bar', () => {
        const barStub = sinon.stub().returns(42);
        cap.__Rewire__('bar', barStub); // <-- Magic happens here
        cap('some');
        expect(barStub.calledOnce).to.be.true;
      });
    });
    

    请务必将rewire 添加到.babelrc 中的babel 插件中:

    // .babelrc
    {
      "presets": [
        "es2015"
      ],
      "plugins": [],
      "env": {
        "test": {
          "plugins": [
            "rewire"
          ]
        }
      }
    }
    

    最后,正如您所见,babel-plugin-rewire 插件仅在测试环境中启用,因此您应该调用您的测试运行程序,并将BABEL_ENV 环境变量设置为test(您可能已经这样做了) :

    env BABEL_ENV=test mocha --compilers js:babel-core/register test-example.js
    

    注意:我无法让 babel-plugin-rewire-exports 工作。

    【讨论】:

      【解决方案5】:

      这对我来说绝对是一个陷阱......

      我创建了一个小工具来解决 sinon 的这个限制。 (在js too 中可用)。

      // mockable.ts ?
      

      import sinon from 'sinon'
      
      export function mockable<T extends unknown[], Ret>(fn: (...fnArgs: T) => Ret) {
        let mock: sinon.SinonStub<T, Ret> | undefined
        const wrapper = (...args: T) => {
          if (mock) return mock(...args)
          return fn(...args)
        }
        const restore = () => {
          mock = undefined
        }
        wrapper.mock = (customMock?: sinon.SinonStub<T, Ret>) => {
          mock = customMock || sinon.stub()
          return Object.assign(mock, { restore })
        }
        wrapper.restore = restore
        return wrapper
      }

      如果你将上面的 sn-p 粘贴到你的项目中,你可以像这样使用它

      // foo.js
      import { mockable } from './mockable'
      
      // we now need to wrap the function we wish to mock
      export const foo = mockable((x) => {
         console.log(x)
      })
      
      // main.js
      import { foo } from './foo'
      
      export const main = () => {
        foo('asdf') // use as normal
      }
      
      // test.js
      import { foo } from './foo'
      import { main } from './main'
      
      // mock the function - optionally pass in your own mock
      const mock = foo.mock()
      
      // test the function
      main()
      console.assert(mock.calledOnceWith('asdf'), 'not called')
      
      // restore the function
      stub.restore()
      
      

      这种方法的好处是您不必记住总是以某种方式导入函数。 import { foo } from './foo'import * as foo from './foo' 一样好用。自动导入可能只适用于您的 IDE。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-05-03
        • 2020-06-30
        • 1970-01-01
        • 2020-05-11
        • 2017-07-07
        • 2016-09-17
        • 2017-02-23
        • 1970-01-01
        相关资源
        最近更新 更多