Search code examples
node.jstypescriptunit-testingjestjsalias

jest cannot resolve module aliases


I am using a npm module called module-alias. I map some modules in tsconfig.json and package.json

tsconfig.json

"compilerOptions": {
   "baseUrl": "./src",
   "paths": {
   "@config/*": ["config/*"],
   "@interfaces/*": ["interfaces/*"],
   "@services/*": ["services/*"]
  },
  "module": "commonjs",
  "target": "es2015",                   /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
  "sourceMap": true,
  "outDir": "./dist",                        /* Redirect output structure to the directory. */
  "rootDir": "./src",    
  "allowSyntheticDefaultImports": true          /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
}

package.json

  ...
 "_moduleAliases": {
    "@config": "src/config",
    "@interface": "src/interface",
    "@services": "src/services"
  },
  "jest": {
    "moduleNameMapper": {
      "@config/(.*)": "src/config/$1",
      "@interface/(.*)": "src/interface/$1",
      "@services/(.*)": "src/services/$1"
    },

     "moduleFileExtensions": ['js', 'json', 'jsx', 'ts', 'tsx', 'node']
  },
...

server.ts

import { logger } from '@config/logger';

everything works fine when I run npm start, but it gives me an error when I run jest

FAIL  src/test/article.spec.ts
  ● Test suite failed to run

    Cannot find module '@config/logger' from 'server.ts'

    However, Jest was able to find:
        'rest/server.ts'

    You might want to include a file extension in your import, or update your 'moduleFileExtensions', which is currently ['js', 'json', 'jsx', 'ts', 'tsx', 'node'].

Anyone know what the problem is? thanks

Solution works for me (Update 18/10/2019) :

Create a jest.config.js with code below:

module.exports = {
  "roots": [
    "<rootDir>/src/"
],
  "transform": {
    "^.+\\.tsx?$": "ts-jest"
  }

}

and update moduleNameMapper in package.json:

...
  "_moduleAliases": {
    "@config": "./src/config",
    "@interfaces": "./src/interfaces",
    "@services": "./src/services"
  },
  "jest": {

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

Solution

  • After a few hours I've managed to make this work. I'll try my best to simplify it to save others time and make it smooth.

    My app is of the following stack:

    • Typescript (tsc and ts-node/register, no Babel) for the API build (.ts)
    • React (.tsx using Webpack)
    • jest for API testing (no Babel)
    • testcafe for UI/E2E
    • VSCode as IDE

    Key notes:

    1. The solution that works for me is must have "@" character in front of the alias. It's not required in theory by module-alias, however Jest is getting lost when we apply the module name mapper.
    2. There needs to be consistency for naming between 'webpack' aliases and 'tsconfig'. It is necessary for VSCode not to red underline module names in TSX files.
    3. This should work regardless of your document structure but remember to adapt baseUrl and jest config if encounter issues.
    4. When applying changes in VSCode for .tsx files do not be worried that some of the paths are underlined. It's temporary as VSCode seems to grasp it only when all files are correctly connected to each other. It demotivated me at the start.

    First, install module-alias from https://www.npmjs.com/package/module-alias with

    npm i --save module-alias
    

    then add to your initial startup file (for .ts files only, i.e. your application server):

    require('module-alias/register')
    

    as the module-alias docs indicate. Then setup tsconfig.ts. Keep it mind that baseUrl is relevant here as well:

    {
     "compilerOptions": {
        "baseUrl": ".",
        ...
        "paths": {
          "@helpers/*": ["src/helpers/*"],
          "@root/*": ["src/*"]
          ...
        }
     }
    

    then setup your webpack.js:

    const path = require('path')
    ...
    {
      resolve: {
        ...
        alias: {
          '@helpers': path.resolve(__dirname, 'src', 'helpers'),
          '@root': path.resolve(__dirname, 'src')
        }
      }
    }
    

    then setup your package.json:

    {
       ...
       "_moduleAliases": {
         "@helpers": "src/helpers",
         "@root": "src"
       }
    }
    

    then setup your jest config (I attach only things that were relevant when applying my change):

    {
       rootDir: ".",
       roots: ["./src"],
       transform: {
         "^.+\\.ts?$": "ts-jest"
       },
       moduleNameMapper: {
         "@helpers/(.*)": "<rootDir>/src/helpers/$1",
         "@root/(.*)": "<rootDir>/src/$1"
       },
       ...
     }
    

    Now, we need to take care of the build process because tsc is not capable of transpiling aliases to their relative siblings.

    To do that we will use tscpaths package: https://github.com/joonhocho/tscpaths . This one is simple.

    So considering your build command was just:

    tsc
    

    Now it becomes

    tsc && tscpaths -p tsconfig.json -s ./src -o ./dist/server
    

    You need to adjust your -s and -o to your structure, but when you inspect your .js file after build you should see if the relative path is correctly linked (and debug accordingly).

    That's it. It should work as ace. It's a lot but it's worth it.

    Example of a call in the controller (.ts) and in React component (.tsx) file:

    import { IApiResponse } from '@intf/IApi'