javascriptnode.jsmockingecmascript-6jestjs

How can I mock an ES6 module import using Jest?


I want to test that one of my ES6 modules calls another ES6 module in a particular way. With Jasmine this is super easy --

The application code:

// myModule.js
import dependency from './dependency';

export default (x) => {
  dependency.doSomething(x * 2);
}

And the test code:

//myModule-test.js
import myModule from '../myModule';
import dependency from '../dependency';

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    spyOn(dependency, 'doSomething');

    myModule(2);

    expect(dependency.doSomething).toHaveBeenCalledWith(4);
  });
});

What's the equivalent with Jest? I feel like this is such a simple thing to want to do, but I've been tearing my hair out trying to figure it out.

The closest I've come is by replacing the imports with requires, and moving them inside the tests/functions. Neither of which are things I want to do.

// myModule.js
export default (x) => {
  const dependency = require('./dependency'); // Yuck
  dependency.doSomething(x * 2);
}

//myModule-test.js
describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    jest.mock('../dependency');

    myModule(2);

    const dependency = require('../dependency'); // Also yuck
    expect(dependency.doSomething).toBeCalledWith(4);
  });
});

For bonus points, I'd love to make the whole thing work when the function inside dependency.js is a default export. However, I know that spying on default exports doesn't work in Jasmine (or at least I could never get it to work), so I'm not holding out hope that it's possible in Jest either.


Solution

  • Fast forwarding to 2020, I found this blog post to be the solution: Jest mock default and named export

    Using only ES6 module syntax:

    // esModule.js
    export default 'defaultExport';
    export const namedExport = () => {};
    
    // esModule.test.js
    jest.mock('./esModule', () => ({
      __esModule: true, // this property makes it work
      default: 'mockedDefaultExport',
      namedExport: jest.fn(),
    }));
    
    import defaultExport, { namedExport } from './esModule';
    defaultExport; // 'mockedDefaultExport'
    namedExport; // mock function
    

    Also one thing you need to know (which took me a while to figure out) is that you can't call jest.mock() inside the test; you must call it at the top level of the module. However, you can call mockImplementation() inside individual tests if you want to set up different mocks for different tests.