Testing with Jest and Webpack aliases

I am looking to be able to use webpack aliases to resolve imports when using jest, and optimally, reference the webpack.aliases to avoid duplication.

Jest conf:

  "jest": {
    "modulePaths": ["src"],
    "moduleDirectories": ["node_modules"],
    "moduleNameMapper": {
      "^@shared$": "<rootDir>/shared/",
      "^@components$": "<rootDir>/shared/components/"
    }
  },

Webpack aliases:

exports.aliases = {
    '@shared': path.resolve(paths.APP_DIR, 'shared'),
    '@components': path.resolve(paths.APP_DIR, 'shared/components'),
};

Imports:

import Ordinal from '@shared/utils/Ordinal.jsx';
import Avatar from '@components/common/Avatar.jsx';

For some reason the @ causes issues, so when removed (in both alias and import), it can find shared but components still cannot be resolved.

 FAIL  src/shared/components/test/Test.spec.jsx
  ● Test suite failed to run

    Cannot find module '@shared/utils/Ordinal.jsx' from 'Test.jsx'

I have tried using jest-webpack-alias, babel-plugin-module-resolver and the Jest/Webpack docs


Solution 1:

This seems to have been fixed.

Below is a working setup:

Versions

"jest": "~20.0.4"

"webpack": "^3.5.6"

package.json

"jest": {
  "moduleNameMapper": {
    "^@root(.*)$": "<rootDir>/src$1",
    "^@components(.*)$": "<rootDir>/src/components$1",
  } 
}

webpack.shared.js

const paths = {
  APP_DIR: path.resolve(__dirname, '..', 'src'),
};

exports.resolveRoot = [paths.APP_DIR, 'node_modules'];

exports.aliases = {
  '@root': path.resolve(paths.APP_DIR, ''),
  '@components': path.resolve(paths.APP_DIR, 'components'),
};

Solution 2:

Since I had the same problem before I read again, and this time more carefully the documentation. Correct config should be:

  "jest": {
    "moduleNameMapper": {
      "^@shared(.*)$": "<rootDir>/shared$1",
      "^@components(.*)$": "<rootDir>/shared/components$1"
    }
  },

Solution 3:

Using: "jest": "^26.5.3", and "webpack": "4.41.5", I was able to properly match my webpack/typescript aliases in the jest.config.js with this pattern:

Webpack config:

module.exports = {
   // the rest of your config
    resolve: {
      alias: {
        'components': path.resolve(__dirname, 'js/app/components'),
        'modules': path.resolve(__dirname, 'js/app/modules'),
        'types': path.resolve(__dirname, 'js/types'),
        'hooks': path.resolve(__dirname, 'js/app/hooks'),
        'reducers': path.resolve(__dirname, 'js/app/reducers'),
        '__test-utils__': path.resolve(__dirname, 'js/app/__test-utils__') 
      }
    },
}

Jest.config.js:

  moduleNameMapper: {
    '^types/(.*)$':  '<rootDir>/js/types/$1',
    '^components/(.*)$': '<rootDir>/js/app/components/$1',
    '^modules/(.*)$':  '<rootDir>/js/app/modules/$1',
    '^hooks/(.*)$':  '<rootDir>/js/app/hooks/$1',
    '^reducers/(.*)$':  '<rootDir>/js/app/reducers/$1',
    '^__test-utils__/(.)$': '<rootDir>/js/app/__test-utils__/$1' 
  }

Here's an explanation of the symbols:

  • (.*)$: capture whatever comes after the exact match (the directory)
  • $1: map it to this value in the directory I specify.

and tsconfig.json:

    "paths": {
      "config": ["config/dev", "config/production"],
      "components/*": ["js/app/components/*"],
      "modules/*": ["js/app/modules/*"],
      "types/*": ["js/types/*"],
      "hooks/*": ["js/app/hooks/*"],
      "reducers/*": ["js/app/reducers/*"],
      "__test-utils__/*": ["js/app/__test-utils__/*"]
    }

Solution 4:

For anyone using @ as the root of their modules, you have to be more specific since other libs can use the @ in node modules.

  moduleNameMapper: {
    "^@/(.*)$": "<rootDir>/src/$1"
  },

It translates to "anything that matches @/ should be sent to <rootDir>/src/<rest of the path>

Solution 5:

FWIW, Try switching the alias order, keep the more specific up and less specific down, e.g.

"moduleNameMapper": {
  "^@components$": "<rootDir>/shared/components/",
  "^@shared$": "<rootDir>/shared/"
}