How to Test ES6 Module Calls with Jest

I want to test that one of my ES6 modules calls another ES6 module in a specific way. With Jasmine, this is straightforward. Here’s a simplified example:

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

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

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 is the equivalent approach using Jest? I’ve tried replacing imports with require and moving them inside the tests/functions, but this is not ideal. Example:

// 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 know if it’s possible to make this work when dependency.js exports a default function. I know that spying on default exports is challenging in Jasmine, so I’m not sure if Jest can handle this either.

I’m looking for a solution to jest mock imported function.

I’ve managed to resolve this issue using a workaround involving import *. This approach works for both named and default exports.

For a named export:

// dependency.js
export const doSomething = (y) => console.log(y);

// myModule.js
import { doSomething } from './dependency';

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

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

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    dependency.doSomething = jest.fn(); // Mock the named export

    myModule(2);

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

For a default export:

// dependency.js
export default (y) => console.log(y);

// myModule.js
import dependency from './dependency'; // No curly braces

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

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

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    dependency.default = jest.fn(); // Mock the default export

    myModule(2);

    expect(dependency.default).toHaveBeenCalledWith(4); // Assert against the default export
  });
});

This method ensures that the jest mock imported function works effectively for both named and default exports.

Building on what Charity mentioned, using only ES6 module syntax, you can mock your modules like this:

// esModule.js
export default 'defaultExport';
export const namedExport = () => {};

In your test file:

// esModule.test.js
jest.mock('./esModule', () => ({
  __esModule: true, // This property is required for ES6 module compatibility
  default: 'mockedDefaultExport',
  namedExport: jest.fn(),
}));

import defaultExport, { namedExport } from './esModule';

describe('ES6 module mocking', () => {
  it('should mock default and named exports', () => {
    expect(defaultExport).toBe('mockedDefaultExport');
    expect(namedExport).toBeInstanceOf(Function); // Checks if namedExport is a mock function
  });
});

Important Note: You must call jest.mock() at the top level of your module, not inside the test functions. However, you can use mockImplementation() within individual tests if you need different mocks for different cases. This approach reinforces the jest mock imported function technique for ES6 modules.

Rashmi’s explanation is great! However, I’ve found that using standard jest.mock() doesn’t always work as expected with ES modules due to their import behavior. Here’s how you can handle this using dynamic imports:

import { describe, expect, it, jest } from '@jest/globals';

// Mock the module using Jest's unstable_mockModule
jest.unstable_mockModule('../dependency', () => ({
  doSomething: jest.fn()
}));

// Use dynamic imports with await
const myModule = await import('../myModule');
const dependency = await import('../dependency');

describe('myModule', () => {
  it('calls the dependency with double the input', () => {
    myModule.default(2); // Call the default export of myModule
    expect(dependency.doSomething).toHaveBeenCalledWith(4);
  });
});

Key Points:

  1. Use jest.unstable_mockModule: This function is used to mock modules when working with dynamic imports.
  2. Dynamic Imports: Use await import() to load your modules after setting up the mocks. This ensures that the mock is applied correctly.
  3. Top-Level Await: You can use top-level await to handle asynchronous imports directly.

This method aligns with the current state of ES module support in Jest and ensures a robust jest mock imported function setup