Jest + Typescript + Absolute paths (baseUrl) gives error: Cannot find module
TypescriptVisual Studio-CodeJestjsCreate React-AppAbsolute PathTypescript Problem Overview
I am setting a configuration to run my tests in a create-react-app + typescript app (from which I have ejected). I am using jest + enzyme. In my tsconfig.json I have set baseUrl='./src'
so I can use absolute paths when I import modules. For example this is a typical import statement in one of my files:
import LayoutFlexBoxItem from 'framework/components/ui/LayoutFlexBoxItem';
You can see that the path is absolute (from /src folder) and not relative.
This works fine when I run in debug mode ( yarn start
)
But when I run my test ( yarn test
), I get this error:
Cannot find module 'framework/components/Navigation' from 'index.tsx'
So it looks like jest is not able to resolve this absolute path although I have set it up in my tsconfig.json. This is my tsconfig.json:
{
"compilerOptions": {
"outDir": "dist",
"module": "esnext",
"target": "es5",
"lib": ["es6", "dom"],
"sourceMap": true,
"allowJs": true,
"jsx": "react",
"moduleResolution": "node",
"rootDir": "src",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true,
"baseUrl": "./src"
},
"exclude": [
"node_modules",
"build",
"dist",
"config",
"scripts",
"acceptance-tests",
"webpack",
"jest",
"src/setupTests.ts"
]
}
Now I can see that there is a generated tsconfig.test.json
at the root of my project. This is the ts configuration used for test. And here is its content:
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs"
}
}
As you can see the "module" is commonjs
here whereas in the default configuration it is esnext
. Could this be one reason?
Has any one been able to unit test his typescript project with Jest and absolute path? or is this a known bug? Since I have ejected from default configuration, are there some settings to put in my webpack configuration?
Thanks for your input and suggestion.
Typescript Solutions
Solution 1 - Typescript
I was struggling with the same problem and actually it turns out that a simple change seems to do the trick.
I just updated the moduleDirectories
field in jest.config.js
.
Before
moduleDirectories: ['node_modules']
After
moduleDirectories: ['node_modules', 'src']
Hope it helps.
Solution 2 - Typescript
As many here pointed out moduleNameMapper
in jest.config.js
needs to define paths specified in tsconfig.json
. For example, if you have paths in tsconfig.json
defined as follows
// tsconfig.json
{
...
"baseUrl": "src",
"paths": {
"@alias/*": [ 'path/to/alias/*' ]
}
...
}
then your jest.config.js
needs to provide those paths in moduleNameMapper
in the following format:
// jest.config.js
module.exports = {
'roots': [
'<rootDir>/src'
],
'transform': {
'^.+\\.tsx?$': 'ts-jest'
},
'moduleNameMapper': {
'@alias/(.*)': '<rootDir>/src/path/to/alias/$1'
}
};
Having that we can improve our jest.config.js
to convert paths defined in tsconfig.json
automatically. Here is a Gist code snippet for that:
// jest.config.js
function makeModuleNameMapper(srcPath, tsconfigPath) {
// Get paths from tsconfig
const {paths} = require(tsconfigPath).compilerOptions;
const aliases = {};
// Iterate over paths and convert them into moduleNameMapper format
Object.keys(paths).forEach((item) => {
const key = item.replace('/*', '/(.*)');
const path = paths[item][0].replace('/*', '/$1');
aliases[key] = srcPath + '/' + path;
});
return aliases;
}
const TS_CONFIG_PATH = './tsconfig.json';
const SRC_PATH = '<rootDir>/src';
module.exports = {
'roots': [
SRC_PATH
],
'transform': {
'^.+\\.tsx?$': 'ts-jest'
},
'moduleNameMapper': makeModuleNameMapper(SRC_PATH, TS_CONFIG_PATH)
};
Solution 3 - Typescript
Here is how I got moduleNameMapper working.
With the below config in my tsconfig:
"paths": {
"@App/*": [
"src/*"
],
"@Shared/*": [
"src/Shared/*"
]
},
Here's the moduleNameMapper:
"moduleNameMapper": {
"@App/(.*)": "<rootDir>/src/$1",
"@Shared/(.*)": "<rootDir>/src/Shared/$1"
}
Solution 4 - Typescript
Add this following section in your package.json. after you made the changes don't forget to restart your test watchers.
"jest": {
"moduleDirectories": [
"node_modules",
"src"
],
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"roots": [
"src"
],
"testRegex": ".spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"coverageDirectory": "../coverage",
"testEnvironment": "node",
"moduleNameMapper": {
"src/(.*)": "<rootDir>/src/$1"
}
}
Solution 5 - Typescript
For me, I just needed to add
"modulePaths": ["<rootDir>/src"],
to my jest.config.js
file.
Following answer to modify moduleDirectories
resulted in this error:
Jest encountered an unexpected token
Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.
Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.
By default "node_modules" folder is ignored by transformers.
Here's what you can do:
• If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
• If you need a custom transformation specify a "transform" option in your config.
• If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.
You'll find more details and examples of these config options in the docs:
https://jestjs.io/docs/configuration
For information about custom transformations, see:
https://jestjs.io/docs/code-transformation
Using:
modulePaths: ["node_modules", "<rootDir>/src"],
From reading the docs it appears that this a list of additional directories and so node_modules
is unnecessary.
Solution 6 - Typescript
For those using an absolute path but not using named mappings, this worked for me:
# jsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
}
}
# jest.config.js
const config = {
moduleDirectories: ['node_modules', '<rootDir>'],
};
Solution 7 - Typescript
Solution using the best practice
This error occurs because of using absolute paths in the import
statements of our TypeScript/Nest.js/Angular projects while using Jest. Fixing it with moduleDirectories
and moduleNameMapper
options may solve your problem temporarily but it creates issues with other packages such as this TypeORM issue. Also, the creator of the Nest.js framework suggests that using absolute paths is a bad practice.
Absolute path vs Relative path
import
statement with absolute path looks like:
import { AuthService } from 'src/auth/auth.service'
import
statement with relative path looks like:
import { AuthService } from '../auth/auth.service'
VS Code Setting
VS Code by default uses absolute path as shown above, when we auto-import using code completion or Command/Ctrl + .. We need to change this default setting to use relative paths.
Go to VS Code settings and search for a setting: Import Module Specifier
. Change it from shortest
to relative
.
Now from here on, VS Code will automatically import using the relative paths.
Fixing imports in the project
Now in the project files, look for the absolute paths in the imports that look like the example above and delete them. You will see errors for the packages that you deleted. Simply use the auto-import suggestions and import them back. This time they will be imported using the relative paths. This step may be tedious depending on the size of your project but it's worth it in the long run.
Hope that works out! Cheers!
Solution 8 - Typescript
ts-jest can resolve this problem perfectly!
https://kulshekhar.github.io/ts-jest/docs/getting-started/paths-mapping#jest-config-with-helper
just modify jest.config.js like this:
const { pathsToModuleNameMapper } = require('ts-jest/utils');
const { compilerOptions } = require('./tsconfig.json');
module.exports = {
// preset is optional, you don't need it in case you use babel preset typescript
preset: 'ts-jest',
// note this prefix option
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, /* { prefix: '<rootDir>/' } */)
}
Solution 9 - Typescript
Here is what worked for me:
npm i -D jest typescript
npm i -D ts-jest @types/jest
npx ts-jest config:init
Then in jest.config.js, here's my setup
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
modulePaths: ["node_modules", "<rootDir>/src"],
};
in my case, I do not have any paths
in tsconfig.json
but I have baseUrl
set to src
Solution 10 - Typescript
I've using React with Typescript, I removed react-scripts-ts test --env=jsdom
from npm test
and added jest --watch
as my default test, after I added jest.config.js
to my project following these instructions https://basarat.gitbooks.io/typescript/docs/testing/jest.html
and I used the the configuration mentioned by @Antonie Laffargue (add/edit property moduleDirectories: ['node_modules', 'src']
), it works perfectly.
Solution 11 - Typescript
If this happens to you in monorepo here's what fixed the problem for me:
Inside jest.config.js
roots: ["<rootDir>packages"],
moduleNameMapper: {
'@monopre/(.+)$': '<rootDir>packages/$1/src',
},
Assuming you have in tsconfig.json
"paths": {
"@monopre/*": ["packages/*/src"],
}
Solution 12 - Typescript
You probably want moduleNameMapper
feature of jest config.
It will map your custom import namespaces to real module locations.
see official documentation here:
https://facebook.github.io/jest/docs/en/configuration.html#modulenamemapper-object-string-string
Solution 13 - Typescript
I had a similar problem. I hope this could help to spare time for some of you.
My problem:
- using create-react-app with typescript
- using absolute paths (src/MyComp) to import components inside other components (e.g. App.tsx)
- it was working on compile/run/build
- it was not working on test
I found that the error was due to a different value of the NODE_PATH. So I set it on tests run.
I recreated the issue and the fix in here: https://github.com/alessandrodeste/...
I'm not sure if this could bring side effects on tests. Let me know if you have feedback ;)
Solution 14 - Typescript
Adding the following to my jest config in package.json resolved this problem for me.
"moduleDirectories": [
"node_modules",
"src"
]
Solution 15 - Typescript
Using Svelte Kit, my solution was:
import { readFileSync } from 'fs';
import pkg from 'ts-jest/utils/index.js';
const { pathsToModuleNameMapper } = pkg;
const { compilerOptions } = JSON.parse(readFileSync('./tsconfig.json'))
export default {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['<rootDir>/**/*.test.ts'],
testPathIgnorePatterns: ['/node_modules/'],
coverageDirectory: './coverage',
coveragePathIgnorePatterns: ['node_modules'],
globals: { 'ts-jest': { diagnostics: false } },
transform: {},
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: '<rootDir>/' }),
}
Solution 16 - Typescript
If you have intalled ts-jest you can to use a util function called pathsToModuleNameMapper to convert the path inside tsconfig.json to your jest.config file:
My jest.config.js file:
const { join } = require('path');
const { pathsToModuleNameMapper } = require('ts-jest')
const { compilerOptions } = require('./tsconfig.json')
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
rootDir: __dirname,
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
setupFiles: ['<rootDir>/src/config/env.ts'],
collectCoverageFrom: ["<rootDir>/src/modules/**/*UseCase.ts"],
coverageProvider: "v8",
coverageThreshold: {
global: {
lines: 40
}
},
bail: true,
clearMocks: true,
displayName: 'unit-tests',
testMatch: ["<rootDir>/src/modules/**/*.spec.ts"],
preset: 'ts-jest',
testEnvironment: 'node',
modulePaths: ["<rootDir>/src"],
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, {
prefix: join('<rootDir>', compilerOptions.baseUrl)
})
};
Solution 17 - Typescript
I had the same problem using StitchesJS, the module was not found, the solution was to put this in my jest.config.js
moduleNameMapper: {
"stitches.config": "<rootDir>/node_modules/@stitches/react/dist/index.cjs"}
You can adapt according to the module you want.