In an ES module Node project, with no Babel, jest.mock works when the mocked module is a node_modules package that exports CommonJS, but it isn't working for me mocking an ES module exported from a file in the same project.
(It's possible that an NPM package that only exports ES modules has the same issue. I didn't try that case.)
Steps to reproduce the behavior:
Click Run in the repl, or here's a simple example:
// main.js
import secondary from "./secondary.js";
export default function main() {
return secondary();
}
// secondary.js
export default function secondary() {
return true;
}
// test.js
import { jest } from "@jest/globals";
jest.mock("./secondary.js");
let main;
let secondary;
beforeAll(async () => {
({ default: main } = await import("./main.js"));
({ default: secondary } = await import("./secondary.js"));
});
test("works", () => {
secondary.mockReturnValueOnce(false); // TypeError: Cannot read property 'mockReturnValueOnce' of undefined
expect(main()).toBe(false);
});
jest.mock(filename) should mock the exports from filename when the test file and the Node project are both ES modules (type: "module")
https://repl.it/repls/VerifiableOfficialZettabyte
System:
OS: macOS 10.15.4
CPU: (4) x64 Intel(R) Core(TM) i5-4278U CPU @ 2.60GHz
Binaries:
Node: 12.16.3 - ~/.nvm/versions/node/v12.16.3/bin/node
Yarn: 1.21.1 - /usr/local/bin/yarn
npm: 6.14.4 - ~/DevProjects/reaction/api-utils/node_modules/.bin/npm
npmPackages:
jest: ^26.0.1 => 26.0.1
Copied from https://github.com/facebook/jest/issues/9430#issuecomment-625418195 at the request of @SimenB. Thanks!
I respectfully disagree with this being labeled a feature request. It's entirely blocking of any effort to move to Jest native ES module support if any files have mocks in them, and there is no workaround that I know of (other than to continue using CommonJS through Babel, which means that ES module support is broken, hence bug).
I started working on this, and I think it makes sense to leave .mock and .doMock for CJS, and introduce a new .mockModule or something for ESM. It will require users to be explicit, and allow the factory to be async. Both if which I think are good things.
Also need to figure out isolateModules. Unfortunately it uses the module name while not being for for ES modules.
@thymikee @jeysal thoughts?
@aldeed @SimenB Hello, I'm also having the same problem, but when I try to use jest with babel instead, I'm running into SyntaxError: Cannot use import statement outside a module (it throws inside an imported module), which is basically what #9430 is all about I guess.
Is there any workaround to mock modules from the same project? Or to prevent the SyntaxError from occurring when using babel .
@guilhermetelles It can be a pain to do, but you'll likely get more help if you create a public minimal reproduction repo for your issue and create a new GH issue that references this one. There are about a dozen things that could cause this issue, from using an older Node version to Babel config issues, and being able to see all the project files is the best way for someone to help you solve it.
@SimenB You mentioned above that you had a start on this and it looks like everyone 馃憤 your proposal. Is there any update?
One thing to note is that it will be impossible to mock import statements as they are evaluated before any code is executed - which means it's not possible to setup any mocks before we load the dependency. So you'll need to do something like this using import expressions.
import { jest } from '@jest/globals';
jest.mockModule('someModule', async () => ({ foo: 'bar' }));
let someModule;
beforeAll(async () => {
someModule = await import('someModule');
});
test('some test', () => {
expect(someModule.foo).toBe('bar');
});
It will be a bit cleaner with top-level await
import { jest } from '@jest/globals';
jest.mockModule('someModule', async () => ({ foo: 'bar' }));
const someModule = await import('someModule');
test('some test', () => {
expect(someModule.foo).toBe('bar');
});
Any modules loaded by someModule via import statements would work though as we'd have time to setup our mocks before that code is evaluated.
The example in the OP follows this pattern, I'm just pointing it out 馃憤
Most helpful comment
I started working on this, and I think it makes sense to leave
.mockand.doMockfor CJS, and introduce a new.mockModuleor something for ESM. It will require users to be explicit, and allow the factory to be async. Both if which I think are good things.Also need to figure out
isolateModules. Unfortunately it uses themodulename while not being for for ES modules.@thymikee @jeysal thoughts?