Jest mocking default exports - require vs import
Asked Answered
S

4

71

I have seen questions referring to the mocking of default exports with jest around here, but I don't think this has already been asked:

When mocking the default export of a dependency of a module that is being tested, the tests suite fails to run if the module imports the dependency with the ES6 import statement, stating TypeError: (0 , _dependency.default) is not a function It succeeds, however, if the module uses a require().default call instead.

In my understanding, import module from location directly translates to const module = require(location).default, so I am very confused why this is happening. I'd rather keep my code style consistent and not use the require call in the original module.

Is there a way to do it?

Test file with mock:

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

jest.mock('./dependency', () => {
   return {
      default: jest.fn()
   };
});

// This is what I would eventually like to call
it('calls the mocked function', () => {
   expect(dependency).toHaveBeenCalled();
});

Dependency.js

export default () => console.log('do something');

module.js (not working)

import dependency from './dependency.js';
dependency();

module.js (working)

const dependency = require('./dependency.js').default;
dependency();
Schooner answered 1/11, 2017 at 14:3 Comment(3)
Is the dependency call happening on a different module from the jest.mock call?Plath
shouldnt jest.mock('./dependecy', () => jest.fn()); work in both cases?Rostand
I updated the code of the test file to show that the module is being tested by simply importing and running it.Schooner
P
58

You can use either es6 import or require js to import your js files in your jest tests.

When using es6 import you should know that jest is trying to resolve all the dependencies and also calls the constructor for the class that you are importing. During this step, you cannot mock it. The dependency has to be successfully resolved, and then you can proceed with mocks.

I should also add that as can be seen here jest by default hoists any jest.mocks to the top of the file so the order in which you place your imports does not really matter.

Your problem though is different. Your mock function assumes that you have included your js file using require js.

jest.mock('./dependecy', () => {
   return {
      default: jest.fn()
   };
});

When you import a file using require js, this is the structure it has:

enter image description here

So assuming I have imported my class called "Test" using require js, and it has method called "doSomething" I could call it in my test by doing something like:

const test = require('../Test');
test.default.doSomething();

When importing it using es6 import, you should do it differently though. Using the same example:

import Test from '../Test';
Test.doSomething();

EDIT: If you want to use es6 import change your mock function to:

jest.mock('./dependecy', () => jest.fn());
Protectorate answered 1/11, 2017 at 16:1 Comment(8)
The module I am testing is not exporting a class or anything at all. I updated the code to show what that. It is importing / requiring a module that dos have a default export, however.Schooner
So my module should import the dependency es6 style. If the mock expects require js, how can I make it expect es6 import?Schooner
@AnnaMelzer I've edited my answer with how your mock function should be.Protectorate
I just curios, but what is _esModule: true, how can I print the structure of an imported file? (not the returned object from the required file)Fowl
@KevinChandra __esModule lets importing modules know that this is a transpiled ES module (which matters especially for default exports).Protectorate
@SotirisKiritsis do you know how do I print this? when I try to print module in var module = require('../module.js') it will only print the module.exports value of module.js so how do I print the structure of module.js?Fowl
In my case I intend to use just default mock: const getErrorMiddleware = require('@XXXX/web-middlewares/error').default; jest.mock('@XXXX/web-middlewares/error'); But it breaks with error: Failed to get mock metadata: ... Any thoughts?Rowena
How could I mock it's implementation differently in separate tests?Shed
H
64

the short answer for ES module if you want to use import dependency from 'dependency'

jest.mock('dependency', () => ({
  ...jest.requireActual('dependency'),
  __esModule: true,
  default: jest.fn(),
}))
Homogony answered 14/1, 2022 at 3:29 Comment(4)
While the accepted answer gives good details on what's going on and why this happens I find this to actually be a cleaner solution. Not sure why it got downvoted, but in a scenario like mine where the codebase is not fully migrated to ES6/ Typescript for imports setting the __esModule option to false did the trick, it allows the use of imports in the modules that use es6 and not have to mix require js within the newer files.Pretzel
If you want to have both a default as well as other exports, you can do it this way. __esModule: true is what's required to handle the default correctly.Tacet
It works also if you want to mock some not default imports with default import, the return alternative allows mocking only default value for example: import Component, {ComponentProps} from './component'Carincarina
in my case I've used: jest.mock('@utils/metrics/useYMABFlags', () => { return { __esModule: true, default: jest.fn(() => { return { flags: {}, isLoading: false, }; }), }; });Work
P
58

You can use either es6 import or require js to import your js files in your jest tests.

When using es6 import you should know that jest is trying to resolve all the dependencies and also calls the constructor for the class that you are importing. During this step, you cannot mock it. The dependency has to be successfully resolved, and then you can proceed with mocks.

I should also add that as can be seen here jest by default hoists any jest.mocks to the top of the file so the order in which you place your imports does not really matter.

Your problem though is different. Your mock function assumes that you have included your js file using require js.

jest.mock('./dependecy', () => {
   return {
      default: jest.fn()
   };
});

When you import a file using require js, this is the structure it has:

enter image description here

So assuming I have imported my class called "Test" using require js, and it has method called "doSomething" I could call it in my test by doing something like:

const test = require('../Test');
test.default.doSomething();

When importing it using es6 import, you should do it differently though. Using the same example:

import Test from '../Test';
Test.doSomething();

EDIT: If you want to use es6 import change your mock function to:

jest.mock('./dependecy', () => jest.fn());
Protectorate answered 1/11, 2017 at 16:1 Comment(8)
The module I am testing is not exporting a class or anything at all. I updated the code to show what that. It is importing / requiring a module that dos have a default export, however.Schooner
So my module should import the dependency es6 style. If the mock expects require js, how can I make it expect es6 import?Schooner
@AnnaMelzer I've edited my answer with how your mock function should be.Protectorate
I just curios, but what is _esModule: true, how can I print the structure of an imported file? (not the returned object from the required file)Fowl
@KevinChandra __esModule lets importing modules know that this is a transpiled ES module (which matters especially for default exports).Protectorate
@SotirisKiritsis do you know how do I print this? when I try to print module in var module = require('../module.js') it will only print the module.exports value of module.js so how do I print the structure of module.js?Fowl
In my case I intend to use just default mock: const getErrorMiddleware = require('@XXXX/web-middlewares/error').default; jest.mock('@XXXX/web-middlewares/error'); But it breaks with error: Failed to get mock metadata: ... Any thoughts?Rowena
How could I mock it's implementation differently in separate tests?Shed
G
23

Have you tried something like this? I was dealing with the default export mocking for months until I found this.

jest.mock('./dependency', () => () => jest.fn());

The idea behind this line is that you are exporting a module that is a function. So you need to let Jest knows that it has to mock all your ./dependency file as a function, that returns a jest.fn()

Gonorrhea answered 12/8, 2021 at 11:18 Comment(0)
C
10

I had to do this in vue and jest:

Even though in the code I had used the default import, in the unit test I had to name the import like this:

import * as someName from 'dependency';

and then i used the strange property '__esModule' to allow jest to work with default constructor like this:

jest.mock('dependency', () => ({
  __esModule: true,
  default: jest.fn(),
}));

And finally in my unit test, i used spy on default constructor with an implementation:

const mySpy = jest.spyOn(someName, 'default').mockImplementation(params => {
 params.doSomeThing();
});
...
expect(mySpy).toBeCalledWith({...})

It worked, and it was the only way it worked

Clove answered 26/4, 2023 at 14:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.