jest: moduleNameMapper - mocks do not work for imported modules of mapped modules using "moduleNameMapper"

I use jest with webpack. Webpack is configured to use alias for some imports:

    alias: {
      shared: path.resolve(process.cwd(), "some-lib/src"),
    },
    modules: ["app", "node_modules", "some-lib"],

some-lib is git submodule added to the project.

part of Jest config from package.json:

    "moduleDirectories": [
      "node_modules",
      "app",
      "shared-lib"
    ],
    "modulePaths": ["<rootDir>/shared-lib"],
    "moduleNameMapper": {
      "^api(.*)$": "<rootDir>/shared-lib/src$1"
    },

When I’m trying to mock imported module in jest it doesn’t work

    jest.mock("shared/utils")
    import { utilFunc } from "shared/utils"

as a result utilFunc is unmocked. I’m using the latest version of jest.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 53
  • Comments: 30 (7 by maintainers)

Most upvoted comments

I have to confirm that currently Jest has serious issues with aliases defined in a webpack config:

  • jest.mock('aliased/module') - doesn’t do automocking
  • jest.genMockFromModule('aliased/module') - cannot find the module

This issue is 1.5 years old. Will it be ever fixed?

I think “it works” but it’s broken maybe?

Example 1: it works

Module with something to mock
// src/moduleWithSomethingToMock.js
import moduleToMock from '../../rootAlias/helpers/moduleToMock';
Test
import moduleWithSomethingToMock from './moduleWithSomethingToMock';

// Here I'm using the alias
jest.mock('rootAlias/helpers/moduleToMock');

Example 2: doesn’t work

Module with something to mock
// src/moduleWithSomethingToMock.js

// Here I'm using the alias
import moduleToMock from 'rootAlias/helpers/moduleToMock';
Test
import moduleWithSomethingToMock from './moduleWithSomethingToMock';

// Using or not the alias, it doesn't mock the module
jest.mock('rootAlias/helpers/moduleToMock');

I can try to tackle this one.

Hello everyone, I just ran into this issue today. Is there anything I can do to help push this issue forward?

I’ve made a minimal example repo where the ModuleNameMapper does not resolve correctly for imports. https://github.com/Kruptein/jest-test-stuff

Running ‘npm run test’ results in: Track.test has behaviour I expect but uses relative imports, whereas the TrackExpected.test is the behaviour with mapped names with wrong behaviour.

Additionally this output shows that something does not seem to resolve correctly:

Cannot find module '@@storage/track/Track' from 'TrackExpected.test.ts'

      4 | it('', () => {
      5 |     const track = new Track();
    > 6 |     console.log( jest.genMockFromModule('@@storage/track/Track'))
        |                       ^
      7 |     console.log( jest.genMockFromModule('../../../src/storage/track/Track'))
      8 |     // console.log(typeof track);
      9 |     // console.log(typeof (Track as any).mock);```

Does anyone have any new information about this, or know whether it’s going to be fixed before the next release? The same problem occurs when using a custom resolver. We use aliases/nonrelative paths almost exclusively so this is a big issue for us adopting jest. As of 23.0.0-charlie.2 it’s still not working for me.

jest.mock('some/module') is able to resolve the module (e.g. does not throw an error) but it doesn’t get automocked; the original is still used. Using an inline mock works, e.g.

jest.mock('some/module', () => {
    return {
      foo: jest.fn()
    };
});

I’ve tried to reproduce it in isolation but haven’t been able to yet, using both relative & aliased/rooted paths for the original import. So it’s doesn’t seem like it’s quite as simple as how @alex-mironov initially observed it, or maybe it manifests differently for when using a custom resolver?

Additional info:

After a bit of tracing I think this is a result of circular dependencies, which while probably not good are valid in ES6 modules. If I can repro in isolation will file a specific issue.

We’re hitting this issue as well - upgrading to 22.2.2 or downgrading to 20.0.4 didn’t work.

Some details:

  • This works properly if you pass a factory (second argument) to jest.mock()
  • I’ve tracked the issue down to jest-runtime.requireMock(). Specifically:
    • If you pass in a relative path that doesn’t need to be mapped, manualMock gets set to null, which eventually calls this._generateMock(from, moduleName);
    • If you pass in a path that needs to be mapped, manualMock gets set to the absolute FS path (even though this isn’t a manual mock). This causes _generateMock to not be called.
  • There also seems to be some wonkiness since in some cases manualMock is a string/path, and in other cases (inside if (fs.existsSync(potentialManualMock))) it’s a boolean with the path in modulePath.

Unfortunately, that’s as far as I got. I attempted a fix but since I’ve only started working with this framework every change I made broke a bunch of tests…

Hopefully this helps someone find the true fix. I’m also happy to help put in a PR with some direction on what the fix should be.

is there any solution for this issue

aliases defined in a webpack config

Jest does not support webpack aliases - you need to use moduleNameMapper (which is what this issue is about).

That said, I dug into this a tiny bit, and I’m not sure why it fail 😅 However, I’ve put together a failing integration test based on @Kruptein’s excellent reproduction (removing typescript and logging), so maybe it will lower the barrier for people wanting to send a PR to fix this 🙂 Please see #7668

That’s doesn’t really seem feasible - to actually support all ways of exports.bla =, module.exports =, module.exports.bla = not to start with if (maybe) module.exports = and whatever export might be transpiled to from different dialects, seems to me like an endless rabbit hole. Maybe if we enforced export syntax, but that isn’t really widespread yet, especially for node_modules. And having that constraint before proper ESM support in Jest also seems weird.

You’re completely correct of course - I write almost exclusively ES201?/TypeScript these days and when I wrote that, in my mind, a module’s public API would always be well-defined 😉

That makes a lot of sense to me, would you mind creating a PR for that? Maybe provide some hint that you might not be able to rely on automocking and that you have to provide a mock factory inline?

I can take a shot for sure when I get a little free time.

Possible solution for some of you:

If it’s any help to anyone, I just got mine working with this:

In my file that is importing the module:

import { TransactionEmail } from 'mail/transactional-email'

Then in my jest config:

 moduleNameMapper: {
    // Force mock import.
    "mail/transactional-email": "<rootDir>/src/mail/__mocks__/transactional-email.ts",
    // Normal module aliases.
    "^mail/(.*)": "<rootDir>/src/mail/$1",
    "^api/(.*)": "<rootDir>/src/$1"
  },

And then you don’t call jest.mock() anywhere, you are basically just creating an alias for that import in the config and that’s it.

For me, I am on latest versions of jest/ts-jest but when I call jest.mock('mail/transactional-email') it does not mock the module, it just fails silently and continues with tests, but the real module gets imported.

To force jest to import the mock module I just mapped the module import path directly to the mocked module. Not exactly a great solution, but I wanted to share regardless if anyone is in a pinch.

To be honest, the docs for module mocking are really confusing in my opinion I think a big part of the issue here might just be outdated or difficult to understand documentation. My understanding here was that if you say to mock src/modulename then ANY import in the jest test environment should look for a mock file in a sibling directory of the imported module named __mocks__ and look for modulename in there instead. Not sure if that is actually the case or not but would love someone to clarify.

I hope this helps someone else! 🍻

I had experienced similar issues with jest v20.0.4 upgrading to v22.2.2 solved that issue.

If someone can find it interesting: in my case the problem was that the order of entries in moduleNameMapper was wrong… Children should always be before parents in that list.