How can I mock the imports of an ES6 module?

后端 未结 8 1477
醉话见心
醉话见心 2020-11-28 21:33

I have the following ES6 modules:

File network.js

export function getDataFromServer() {
  return ...
}

File widget.js<

相关标签:
8条回答
  • 2020-11-28 21:37

    vdloo's answer got me headed in the right direction, but using both CommonJS "exports" and ES6 module "export" keywords together in the same file did not work for me (Webpack v2 or later complains).

    Instead, I'm using a default (named variable) export wrapping all of the individual named module exports and then importing the default export in my tests file. I'm using the following export setup with Mocha/Sinon and stubbing works fine without needing rewire, etc.:

    // MyModule.js
    let MyModule;
    
    export function myfunc2() { return 2; }
    export function myfunc1() { return MyModule.myfunc2(); }
    
    export default MyModule = {
      myfunc1,
      myfunc2
    }
    
    // tests.js
    import MyModule from './MyModule'
    
    describe('MyModule', () => {
      const sandbox = sinon.sandbox.create();
      beforeEach(() => {
        sandbox.stub(MyModule, 'myfunc2').returns(4);
      });
      afterEach(() => {
        sandbox.restore();
      });
      it('myfunc1 is a proxy for myfunc2', () => {
        expect(MyModule.myfunc1()).to.eql(4);
      });
    });
    
    0 讨论(0)
  • 2020-11-28 21:38

    I recently discovered babel-plugin-mockable-imports which handles this problem neatly, IMHO. If you are already using Babel, it's worth looking into.

    0 讨论(0)
  • 2020-11-28 21:42

    I haven't tried it myself, but I think mockery might work. It allows you to substitute the real module with a mock that you have provided. Below is an example to give you an idea of how it works:

    mockery.enable();
    var networkMock = {
        getDataFromServer: function () { /* your mock code */ }
    };
    mockery.registerMock('network.js', networkMock);
    
    import { Widget } from 'widget.js';
    // This widget will have imported the `networkMock` instead of the real 'network.js'
    
    mockery.deregisterMock('network.js');
    mockery.disable();
    

    It seems like mockery isn't maintained anymore and I think it only works with Node.js, but nonetheless, it's a neat solution for mocking modules that are otherwise hard to mock.

    0 讨论(0)
  • 2020-11-28 21:43

    carpeliam is correct, but note that if you want to spy on a function in a module and use another function in that module calling that function, you need to call that function as part of the exports namespace, otherwise the spy won't be used.

    Wrong example:

    // File mymodule.js
    
    export function myfunc2() {return 2;}
    export function myfunc1() {return myfunc2();}
    
    // File tests.js
    import * as mymodule
    
    describe('tests', () => {
        beforeEach(() => {
            spyOn(mymodule, 'myfunc2').and.returnValue = 3;
        });
    
        it('calls myfunc2', () => {
            let out = mymodule.myfunc1();
            // 'out' will still be 2
        });
    });
    

    Right example:

    export function myfunc2() {return 2;}
    export function myfunc1() {return exports.myfunc2();}
    
    // File tests.js
    import * as mymodule
    
    describe('tests', () => {
        beforeEach(() => {
            spyOn(mymodule, 'myfunc2').and.returnValue = 3;
        });
    
        it('calls myfunc2', () => {
            let out = mymodule.myfunc1();
            // 'out' will be 3, which is what you expect
        });
    });
    
    0 讨论(0)
  • 2020-11-28 21:43

    I implemented a library that attempts to solve the issue of run time mocking of TypeScript class imports without needing the original class to know about any explicit dependency injection.

    The library uses the import * as syntax and then replaces the original exported object with a stub class. It retains type safety so your tests will break at compile time if a method name has been updated without updating the corresponding test.

    This library can be found here: ts-mock-imports.

    0 讨论(0)
  • 2020-11-28 21:45

    I've started employing the import * as obj style within my tests, which imports all exports from a module as properties of an object which can then be mocked. I find this to be a lot cleaner than using something like rewire or proxyquire or any similar technique. I've done this most often when needing to mock Redux actions, for example. Here's what I might use for your example above:

    import * as network from 'network.js';
    
    describe("widget", function() {
      it("should do stuff", function() {
        let getDataFromServer = spyOn(network, "getDataFromServer").andReturn("mockData")
        let widget = new Widget();
        expect(getDataFromServer).toHaveBeenCalledWith("dataForWidget");
        expect(otherStuff).toHaveHappened();
      });
    });
    

    If your function happens to be a default export, then import * as network from './network' would produce {default: getDataFromServer} and you can mock network.default.

    0 讨论(0)
提交回复
热议问题