绝对路径(baseUrl)出现错误:找不到模块。

177

我正在设置一个配置,以在create-react-app + typescript应用程序中运行我的测试(我已经弹出)。 我正在使用jest + enzyme。 在我的tsconfig.json中,我设置了baseUrl='./src',因此当我导入模块时,我可以使用绝对路径。 例如,这是我文件中的典型导入语句:

import LayoutFlexBoxItem from 'framework/components/ui/LayoutFlexBoxItem';

您可以看到路径是绝对的(从/src文件夹),而不是相对的。 当我在调试模式下运行时(yarn start),这很好用。

但是当我运行我的测试(yarn test)时,我会收到以下错误:

 Cannot find module 'framework/components/Navigation' from 'index.tsx'

看起来jest无法解析这个绝对路径,尽管我已经在tsconfig.json中设置了它。这是我的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"
  ]
}

现在我可以看到项目根目录下生成了一个tsconfig.test.json。这是用于测试的ts配置文件。以下是它的内容:
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "module": "commonjs"
  }
}

正如您所看到的,“module”在此处为commonjs,而在默认配置中为esnext。这可能是一个原因吗?

有人能够使用Jest和绝对路径对其TypeScript项目进行单元测试吗?还是这是一个已知的错误?由于我已经从默认配置中退出,是否有一些设置要放在我的webpack配置中?

感谢您的意见和建议。

21个回答

216

我遇到了同样的问题,实际上一个简单的更改似乎就解决了这个问题。

我只是在jest.config.js中更新了moduleDirectories字段。

之前

moduleDirectories: ['node_modules']

之后

moduleDirectories: ['node_modules', 'src']

4
我在 package.json 文件中的 jest 部分添加了以下内容。 - Zartosht Sepideman
31
很明显,如果你在导入时包含了"src/..."等路径,那么请加上这个:moduleDirectories: ['node_modules', '.']。请注意不要改变原意,但需要使语言更通俗易懂。 - Maciej Krawczyk
1
@redcartel 将此行添加到您的.vscode/settings.json文件中,或在UI中更改设置:"javascript.preferences.importModuleSpecifier": "non-relative" - Archibald
@MaciejKrawczyk 谢谢,如果你的 tsconfig 中的 baseUrl./,那么这肯定有效。 - Ilya Kushlianski
更新配置后,请确保重新启动您的监视器! - Greg
显示剩余3条评论

101

正如许多人在这里指出的那样,在 jest.config.js 中的 moduleNameMapper 需要定义在 tsconfig.json 中指定的路径。例如,如果你在 tsconfig.json 中定义了以下路径

// tsconfig.json
{
 ...
 "baseUrl": "src",
 "paths": {
    "@alias/*": [ 'path/to/alias/*' ]
 }
 ...
}

那么你的jest.config.js需要以以下格式在moduleNameMapper中提供这些路径:

// jest.config.js
module.exports = {
    'roots': [
        '<rootDir>/src'
    ],
    'transform': {
        '^.+\\.tsx?$': 'ts-jest'
    },
    'moduleNameMapper': {
         '@alias/(.*)': '<rootDir>/src/path/to/alias/$1'
    }
};

有了这个功能,我们可以改进我们的 jest.config.js 以自动转换在 tsconfig.json 中定义的路径。以下是实现代码的Gist片段

// 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)
};

谢谢。对于别名定义的多个位置(例如"/opt/*": ["first/*", "second/*"]),它是如何处理的? - morgler
无法工作对我来说 无法找到映射为@/c/utils/http的模块: 更新:仅使用<rootDir>而不是<rootDir>/src可以工作。 - Kristi Jorgji
在单一代码库中,项目根目录用于启动测试,但是tsconfig和jest配置文件位于其中一个子目录中(例如前端),这样怎么样呢?仍然会出现未找到的错误。我认为这与<rootDir>解析有关。 - Juha Untinen
我喜欢makeModuleNameMapper函数,但老实说我认为我们不应该这样做。这只是我的个人意见。原因是它是代码库中的另一个代码,即使它很简单,也可能会让其他开发人员感到困惑,他们将处理潜在的未来问题。我支持约定优于配置,但这是配置文件。 - dominikj111
2
非常感谢!我已经为此苦苦挣扎了数小时。 - NullPointer

61

以下是我是如何使moduleNameMapper工作的。

通过我的tsconfig中的以下配置:

    "paths": {
      "@App/*": [
        "src/*"
      ],
      "@Shared/*": [
        "src/Shared/*"
      ]
    },

这是moduleNameMapper:

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

49
请在package.json中添加以下内容。修改后不要忘记重新启动测试监视器。
  "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"
    }
  }

7
当我在使用nestjs框架时遇到问题时,这个方法对我有用。我编辑了package.json文件,现在能够使用绝对路径运行我的测试了。 - gian848396
如前所述,如果使用NEST框架,这将非常出色。 - lucaswxp
这个也帮助了我使用NestJS,谢谢! - Paul Şular
这个解决方案对你来说还有效吗? - Norfeldt
这在我的create-react-app中能够正常工作。 - Md Rafee
显示剩余2条评论

29

对我来说,我只需要添加

"modulePaths": ["<rootDir>/src"],

对我的jest.config.js文件进行修改。

按照以下答案修改moduleDirectories会导致此错误:

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

使用:

modulePaths: ["node_modules", "<rootDir>/src"],

从阅读文档来看,这似乎是一些额外目录的列表,因此node_modules是不必要的。


3
不确定为什么这个答案被踩了——这是一个干净的答案,在新版本的jest中完美运行(至少在我这里是v27+),并且在文档中也是解决此问题的推荐方案。唯一需要注意的是,如果你的jest配置文件在项目根目录下,则在大多数情况下实际上不需要<rootDir>这部分,src也可以正常工作。 - xyzen
就这样,谢谢!我已经为此苦思冥想了一个小时! - Steve Moretz
@xyzen 谢谢,./src 也可以,但 src 不行。 - Steve Moretz
这节省了我大量的时间!非常感谢你! - Barry Michael Doyle
1
不错!对我来说,它是"modulePaths": ["<rootDir>/src"],,因为我喜欢用src/开头的绝对路径。 - Dominic
这只是我的情况,我不使用任何别名。只使用ab路径的简化版本。 - Phạm Huy Phát

21

对于那些使用绝对路径但不使用命名映射的人,这对我很有用:

# jsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
  }
}

# jest.config.js
const config = {
  moduleDirectories: ['node_modules', '<rootDir>'],
};


这就是我需要的。 - idiglove

10

使用最佳实践的解决方案

这个错误是因为在我们的TypeScript/Nest.js/Angular项目中,使用了绝对路径的import语句而在使用Jest时出现的。使用moduleDirectoriesmoduleNameMapper选项进行修复可能会暂时解决您的问题,但它会导致其他包(例如TypeORM issue)出现问题。此外,Nest.js框架的创建者建议在这里在这里,使用绝对路径是一种不好的做法。TypeScript官方文档建议我们使用相对路径来引用自己的模块。


绝对路径 vs 相对路径

import 语句使用 绝对路径 的样子如下:

import { AuthService } from 'src/auth/auth.service'

import语句使用相对路径的形式如下:

import { AuthService } from '../auth/auth.service'

VS Code 设置

默认情况下,VS Code 使用绝对路径,如上所示,当我们使用代码完成或 Command/Ctrl + . 进行自动导入时。我们需要更改此默认设置以使用相对路径。

进入 VS Code 设置并搜索设置:Import Module Specifier。将其从 shortest 更改为 relative

现在,从这里开始,VS Code 将自动使用相对路径进行导入。


修复项目中的导入问题

现在,在项目文件中查找类似上面示例的绝对路径导入,并将其删除。您将看到已删除包的错误。只需使用自动导入建议并重新导入它们。这次它们将使用相对路径导入。这一步骤可能会因项目规模而繁琐,但从长远来看是值得的。


希望那能行!干杯!


注意:所提到的ESLint规则和解释是指实际的绝对路径,例如'/home/me/myproject/src/auth/auth.service',而不是上面的示例。 - Tereza Tomcova

6

ts-jest可以完美地解决这个问题!
https://kulshekhar.github.io/ts-jest/docs/getting-started/paths-mapping#jest-config-with-helper
只需像这样修改jest.config.js:

    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>/' } */)
    }

5
如果您已经安装了ts-jest,可以使用一个名为pathsToModuleNameMapper的实用函数将tsconfig.json中路径转换为jest.config文件:
我的jest.config.js文件:
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)
 })
};

应该使用 resetMocks 而不是 clearMocks。 - Jeremy
我在我的集成测试目录(在我的src目录之外)中遇到了错误“无法从'src/server.ts'找到模块'~/routes'”,这个解决方案对我非常有效。谢谢。 - Stretch0

5

我在tsconfig.json中使用了"baseUrl": "./",没有使用任何别名。

jest.config.ts中的moduleDirectories: ['node_modules', '<rootDir>']对我很有用。

现在我可以轻松地导入本地模块,例如:import { Hello } from "src/modules/hello"

jest.config.ts

/*
 * For a detailed explanation regarding each configuration property and type check, visit:
 * https://jestjs.io/docs/configuration
 */
export default {
  clearMocks: true,
  collectCoverageFrom: ['**/*.(t|j)s'],
  coverageDirectory: 'coverage',
  coverageProvider: 'v8',
  moduleDirectories: ['node_modules', '<rootDir>'],
  moduleFileExtensions: ['js', 'json', 'ts'],
  roots: ['src', 'test'],
  setupFiles: ['dotenv/config'],
  testRegex: ['.*\\.spec\\.ts$'],
  transform: {
    '^.+\\.(t|j)s$': 'ts-jest',
  },
};

tsconfig.json

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "es2017",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "esModuleInterop": true,
    "incremental": true,
    "skipLibCheck": true,
    "strictNullChecks": false,
    "noImplicitAny": false,
    "strictBindCallApply": false,
    "forceConsistentCasingInFileNames": false,
    "noFallthroughCasesInSwitch": false,
    "resolveJsonModule": true
  },
  "include": ["src/**/*", "src/**/*.json", "test/**/*"]
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接