Как использовать `import.meta` При тестировании с помощью Jest

#typescript #jestjs #babeljs

#typescript #jestjs #babeljs

Вопрос:

Я пишу Node.js код (на машинописном языке) с использованием ESModules, к которому мне нужен доступ __dirname . Чтобы получить доступ к ESM-эквиваленту __dirname in CommonJS, я вызываю dirname(fileURLToPath(import.meta.url)) . Я также пишу тесты в Jest с помощью TypeScript. Используя это руководство, я настроил Babel. Когда я запускаю jest команду, я получаю

 const DIRNAME = (0, _path.dirname)((0, _url.fileURLToPath)(import.meta.url));
                                                                      ^^^^
SyntaxError: Cannot use 'import.meta' outside a module
  

Вот файлы, которые я написал

someCode.ts :

 import { dirname } from "path";
import { fileURLToPath } from "url";

const DIRNAME = dirname(fileURLToPath(import.meta.url));

export const x = 5;
  

someCode.test.ts :

 import { x } from "./someCode";

it("Returns 5 as the result", () => {
    expect(x).toEqual(5);
});
  

.babelrc.json :

 {
    "presets": [
        ["@babel/preset-env", { "targets": { "node": "current" } }],
        "@babel/preset-typescript"
    ]
}
  

tsconfig.json:

 {
    "compilerOptions": {
        "target": "ES2020",
        "module": "ESNext",
        "moduleResolution": "node"
    },
    "include": ["./src/**/*.ts", "./src/**/*.js"]
}
  

package.json :

 {
    "name": "test",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "type": "module",
    "scripts": {
        "test": "jest"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "devDependencies": {
        "@babel/core": "^7.12.7",
        "@babel/preset-env": "^7.12.7",
        "@babel/preset-typescript": "^7.12.7",
        "jest": "^26.6.3",
        "typescript": "^4.1.2"
    }
}
  

Окружающая среда:

  • Узел: 14.1.0
  • Смотрите package.json версии модулей

Комментарии:

1. Вы следуете общему руководству, в котором используются модули CommonJS. Для встроенного ESM см. jestjs.io/docs/en/ecmascript-modules

2. Я ищу ответ на тот же вопрос. Перемещение Jest в модули ESM кажется чрезмерным.

Ответ №1:

Решение: модульизировать код ESM и смоделировать его.

Я также недавно столкнулся с этой проблемой, и мне пришлось:

  1. Экспортируйте конкретную проблему ESM, например import.meta , функциональность, в собственный файл / функцию «utils».
  2. Затем создайте макет функции, которая не требует специфичной для ESM функциональности, в каталоге mocks.
  3. в файлах, которые используют эту функциональность, объявите jest.mock("/path/to/that/file.ts")

Процесс будет выглядеть примерно так:

Исходная структура файла

 src/
--/someCode.ts
--/__tests__/
/--/--/someCode.test.ts
  
 // someCode.ts
...
const getApiUrl = () => { 
  ...
  const url = import.meta.env.SOME_ENV_VAR_HERE;
  ...
  return url;
};
...
  

Новая структура файла

Заголовок

 src/
--/someCode.ts
--/utils.js
--/__mocks__/
--/--/utils.ts
--/__tests__/
--/--/someCome.test.ts
  

а затем в самих файлах:

 // someCode.ts
...
import { getApiUrl } from "./utils.ts";
...

// utils.js
export const getApiUrl = () => {...};
  

и для тестов:

 // __mocks__/utils.ts
export const getApiUrl = jest.fn(() => 'some.url');

// __tests__/utils.test.ts
...
jest.mock("src/util.ts");
...
describe(...);
  

Комментарии:

1. Это то, что привело к моему решению. Единственная разница в том, что я делаю что-то вроде этого: «jest.mock(‘$ lib / variables’, () => ({ __ esModule: true, переменные: { apiUrl: ‘ localhost:2222 ‘ } })); «

2. @TimoHermans не могли бы вы опубликовать решение, которое сработало для вас в ответ

3. это решение немного шаткое. Он работает в спецификации самого root, но по какой-то причине может привести к неправильному импорту макетов файлов в другом месте. Сейчас я изучаю решение babel

4. Это решило проблему для меня. Большое спасибо.

Ответ №2:

Мы используем import.meta.url для использования web workers, например, используя встроенную поддержку worker в Webpack 5. Это отлично работает, но не работает при запуске тестов Jest.

babel-vite-preset насколько я могу судить, не справляется с этим. Главный ответ об извлечении и издевательстве над используемым кодом import.meta действительно работает, но громоздок.

Я обнаружил, что в настоящее время лучшим решением является использование babel-plugin-transform-import-meta . Этот простой плагин заменяет import.meta.url синтаксис на Node.js код для получения URL-адреса текущего файла, чтобы он хорошо работал в тестах. Обратите внимание, что вы захотите, чтобы этот плагин был активен только при запуске тестов.

Комментарии:

1. Спасибо, именно то, что мне нужно!

2. Как заставить его работать только в jest? Я пытался заставить что-то работать с create-react-app 5 / webpack 5 здесь, но не смог запустить тесты github.com/cmdcolin/cra-webpack5-web-worker-example

3. @ColinD способ, которым я это сделал, заключался в том, чтобы добавлять плагин только в «тестовой» среде, что-то вроде этого в babel.config.js : gist.github.com/cmlenz/da2b8b9aa959d24b2d300a8a04a23727 . Не знаю о CRA

4. потрясающе, спасибо! случайный другой вопрос: действительно ли он запускается в «worker», когда он использует jest таким образом?

5. Для тех, кто читает env вместо url, вы можете использовать следующий пакет: npmjs.com/package/babel-plugin-transform-vite-meta-env

Ответ №3:

У меня была такая же проблема, и я исправил ее с помощью плагина babel: search-and-replace

В моем коде я изменил все import.meta.url с import_meta_url

И я добавляю плагин в конфигурацию babel, чтобы изменить его import.meta.url при разработке и производстве и с помощью resolve(__dirname, 'workers')

Я смог это сделать, потому что он соответствовал всем моим случаям, если ваши import_meta_url потребности должны быть заменены на разные укусы в разных сценариях, которые вам нужно будет использовать import_meta_url_1 import_meta_url_2 и т.д. например

наконец, мой babel.config.js

 const { resolve } = require('path');

module.exports = (api) => {
  api.cache.using(() => process.env.NODE_ENV);

  if (process.env.NODE_ENV === 'test') {
    return {
      presets: ['next/babel'],
      plugins: [
        [
          'search-and-replace',
          {
            rules: [
              {
                searchTemplateStrings: true,
                search: 'import_meta_url',
                replace: resolve(__dirname, 'workers'),
              },
            ],
          },
        ],
      ],
    };
  }

  return {
    presets: ['next/babel'],
    plugins: [
      [
        'search-and-replace',
        {
          rules: [
            {
              searchTemplateStrings: true,
              search: 'import_meta_url',
              replace: 'import.meta.url',
            },
          ],
        },
      ],
    ],
  };
};

  

GL

Комментарии:

1. Я использовал ту же конфигурацию в своем babel.config.js но это не сработало. Я получаю следующую ошибку TypeError: Failed to construct 'URL': Invalid base URL

2. search-and-replace имя пакета было обновлено до babel-plugin-search-and-replace

Ответ №4:

Я также столкнулся с этой проблемой. Я смог решить проблему, динамически вставляя правильное значение во время обработки babel. Вместо search-and-replace предложенного плагина babel я использовал макрос codegen для babel.

 // someCode.ts
import { dirname } from "path";
import { fileURLToPath } from "url";
import codegen from 'codegen.macro';

const DIRNAME = dirname(fileURLToPath(codegen`module.exports = process.env.NODE_ENV === "test" ? "{ADD VALID VALUE FOR TESTS}" : "import.meta.url"`));

export const x = 5;
  

Ответ №5:

babel-vite-предустановка

Я думаю, что это здорово.

неофициальный.

вы можете использовать только babel-plugin-transform-vite-meta-env плагин или использовать весь пресет и настроить конфигурацию, как показано ниже

 {
  "presets": [
    [
      "babel-preset-vite",
      {
        "env": true, // defaults to true
        "glob": false // defaults to true
      }
    ]
  ]
}
  

Комментарии:

1. Это сработало для меня. Единственная проблема заключалась в том, что при доступе к другим свойствам, отличным env от or glob , например import.meta.hot , тогда это не сработало бы

2. Этот плагин работает для меня!. yarn add -D babel-plugin-transform-vite-meta-env Затем установите, в babel.config.js добавить { "plugins": ["babel-plugin-transform-vite-meta-env"] }

Ответ №6:

Я нашел решение, которое настолько нелепо, что я даже не поверил, когда оно сработало:

Создайте файл commonjs, например «currentPath.cjs», внутри него поместите что-то вроде:

 module.exports = __dirname;
  

В вашем модуле используйте:

 import * as currentPath from "./currentPath.cjs";
console.log(currentPath.default);
  

И наблюдайте, как происходит волшебство.
PS: используйте path.normalize(), прежде чем использовать его для чего-либо.

Ответ №7:

Внутри вашего tsconfig.json изменения на

     {
    "compilerOptions": {
        "target": "ES2020",
        "module": "ES2020",
        "moduleResolution": "node"
    },
    "include": ["./src/**/*.ts", "./src/**/*.js"]
}
  

Комментарии:

1. Это не сработало.

Ответ №8:

Я боролся с этим в течение Node.js , Экспресс, машинопись, Шутка, проект ts-jext. Для меня проблема заключалась в правильной настройке jest.config.json.

Я публикую свои файлы на случай, если это кому-то поможет.

PACKAGE.JSON

 {
  "name": "express-react",
  "version": "0.0.0",
  "private": true,
  "main": "dist/index.js",
  "type": "module",
  "engines": {
    "node": ">=14.0.0"
  },
  "scripts": {
    "start:dev": "npm run build amp;amp; npm run dev",
    "build": "npm run format amp;amp; npm run lint amp;amp; npm run tsc",
    "start:prod": "npm run build amp;amp; node --experimental-specifier-resolution=node dist/index.js",
    "start": "npm run start:prod",
    "dev": "cross-env-shell DEBUG=express:* node --experimental-specifier-resolution=node --loader ts-node/esm ./src/index.ts",
    "format": "prettier --write "**/*.ts"",
    "lint": "eslint . --ext 'ts,json'",
    "tsc": "tsc",

    "test": "cross-env-shell TS_JEST_LOG=ts-jest.jog NODE_OPTIONS=--experimental-vm-modules jest --detectOpenHandles --verbose --testTimeout 20000 --coverage=true --reporters=default --json --debug --outputFile=./test/log.json",
    "test:list": "jest --detectOpenHandles --coverage=true --listTests --debug"

  },
  "dependencies": {

    "cookie-parser": "~1.4.4",
    "cors": "^2.8.5",
    "debug": "~2.6.9",
    "dotenv": "^16.0.0",
    "express": "~4.16.1",
    "express-fileupload": "^1.3.1",
    "http-errors": "^1.6.3",
    "into-stream": "^7.0.0",
    "jest": "^28.1.0",
    "morgan": "~1.9.1",
    "multer": "^1.4.4",
    "save-dev": "0.0.1-security",
    "sharp": "^0.30.4",
    "supertest": "^6.2.3",
    "ts-jest": "^28.0.2",
    "ts-node": "^10.7.0",
    "uuid": "^8.3.2"
  },
  "devDependencies": {
    "@types/jest": "^27.5.1",
    "@types/supertest": "^2.0.12",
    "@azure/storage-blob": "^12.9.0",
    "@types/cookie-parser": "^1.4.3",
    "@types/cors": "^2.8.12",
    "@types/debug": "^4.1.7",
    "@types/express": "^4.16.1",
    "@types/express-fileupload": "^1.2.2",
    "@types/http-errors": "^1.8.2",
    "@types/into-stream": "^3.1.1",
    "@types/morgan": "^1.9.3",
    "@types/multer": "^1.4.7",
    "@types/node": "^17.0.31",
    "@types/sharp": "^0.30.2",
    "@types/uuid": "^8.3.4",
    "@typescript-eslint/eslint-plugin": "^5.22.0",
    "@typescript-eslint/parser": "^5.22.0",
    "cross-env": "^7.0.3",
    "eslint": "^8.15.0",
    "eslint-config-prettier": "^8.5.0",
    "eslint-plugin-import": "^2.26.0",
    "eslint-plugin-jsx-a11y": "^6.5.1",
    "eslint-plugin-node": "^11.1.0",
    "nodemon": "^2.0.16",
    "prettier": "^2.6.2",
    "tslib": "^2.4.0",
    "typescript": "^4.6.4"
  }
}
  

TSCONFIG.JSON

 {
  "name": "express-react",
  "version": "0.0.0",
  "private": true,
  "main": "dist/index.js",
  "type": "module",
  "engines": {
    "node": ">=14.0.0"
  },
  "scripts": {
    "start:dev": "npm run build amp;amp; npm run dev",
    "build": "npm run format amp;amp; npm run lint amp;amp; npm run tsc",
    "start:prod": "npm run build amp;amp; node --experimental-specifier-resolution=node dist/index.js",
    "start": "npm run start:prod",
    "dev": "cross-env-shell DEBUG=express:* node --experimental-specifier-resolution=node --loader ts-node/esm ./src/index.ts",
    "format": "prettier --write "**/*.ts"",
    "lint": "eslint . --ext 'ts,json'",
    "tsc": "tsc",

    "test": "cross-env-shell TS_JEST_LOG=ts-jest.jog NODE_OPTIONS=--experimental-vm-modules jest --detectOpenHandles --verbose --testTimeout 20000 --coverage=true --reporters=default --json --debug --outputFile=./test/log.json",
    "test:list": "jest --detectOpenHandles --coverage=true --listTests --debug"

  },
  "dependencies": {

    "cookie-parser": "~1.4.4",
    "cors": "^2.8.5",
    "debug": "~2.6.9",
    "dotenv": "^16.0.0",
    "express": "~4.16.1",
    "express-fileupload": "^1.3.1",
    "http-errors": "^1.6.3",
    "into-stream": "^7.0.0",
    "jest": "^28.1.0",
    "morgan": "~1.9.1",
    "multer": "^1.4.4",
    "save-dev": "0.0.1-security",
    "sharp": "^0.30.4",
    "supertest": "^6.2.3",
    "ts-jest": "^28.0.2",
    "ts-node": "^10.7.0",
    "uuid": "^8.3.2"
  },
  "devDependencies": {
    "@types/jest": "^27.5.1",
    "@types/supertest": "^2.0.12",
    "@azure/storage-blob": "^12.9.0",
    "@types/cookie-parser": "^1.4.3",
    "@types/cors": "^2.8.12",
    "@types/debug": "^4.1.7",
    "@types/express": "^4.16.1",
    "@types/express-fileupload": "^1.2.2",
    "@types/http-errors": "^1.8.2",
    "@types/into-stream": "^3.1.1",
    "@types/morgan": "^1.9.3",
    "@types/multer": "^1.4.7",
    "@types/node": "^17.0.31",
    "@types/sharp": "^0.30.2",
    "@types/uuid": "^8.3.4",
    "@typescript-eslint/eslint-plugin": "^5.22.0",
    "@typescript-eslint/parser": "^5.22.0",
    "cross-env": "^7.0.3",
    "eslint": "^8.15.0",
    "eslint-config-prettier": "^8.5.0",
    "eslint-plugin-import": "^2.26.0",
    "eslint-plugin-jsx-a11y": "^6.5.1",
    "eslint-plugin-node": "^11.1.0",
    "nodemon": "^2.0.16",
    "prettier": "^2.6.2",
    "tslib": "^2.4.0",
    "typescript": "^4.6.4"
  }
}
  

JEST.CONFIG.JS

 /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
  clearMocks: true,
  moduleFileExtensions: ["js", "ts", "json", "node"],
  roots: ["./"],
  testMatch: ["./**/*.test.ts"],
  preset: "ts-jest",
  testEnvironment: "node",
  transform: {
    "^. \.ts?$": "ts-jest",
  },
  transformIgnorePatterns: [
    "./node_modules/",
    "./dist/",
    "./files/",
    "./public/",
  ],
  extensionsToTreatAsEsm: ['.ts'],
  globals: {
    "ts-jest": {
      tsconfig: './tsconfig.json',
      useESM: true
    }
  }
};
  

Комментарии:

1. Похоже, что ваш «TSCONFIG.JSON» на самом деле является повторением вашего package.json

Ответ №9:

1. измените свой tsconfig следующим образом

 compilerOptions": {
       "module": "esnext",
       //... remaining options 
     }
  

2. добавьте ниже в jest.config

   globals: {
     "ts-jest": {
      tsconfig: false,
       useESM: true,
      babelConfig: true,
       plugins: ["babel-plugin-transform-vite-meta-env"],
     },
    },
   transform: {
      "^. \.(js|jsx|ts)$": "babel-jest",
    }
`
  

3. Теперь он начнет выдавать ошибку Невозможно использовать import.meta Для решения этой проблемы добавьте ниже в babel config:
`

 module.exports = function (api) {
 
 api.cache(true);
  const presets = [
   ["@babel/preset-env", { targets: { node: "current" } }],
  "@babel/preset-typescript",
  "@babel/preset-react",
];

return {
  presets,
  plugins: [
    "@babel/plugin-transform-runtime",
    "babel-plugin-transform-import-meta",
    "babel-plugin-transform-vite-meta-env",
   ],
  };
 };` 
  

@babel/plugin-transform-runtime, babel-plugin-transform-import-meta эти плагины помогут вам решить эту проблему

Ответ №10:

У меня такая же проблема с Jest и ESM, когда я пытался получить доступ __dirname по-новому. Следует отметить, @babel что в моем проекте его нет, но я думаю, что решение по-прежнему надежно.

Я пытался изменить module и target tsconfig.json безрезультатно.

Кроме того, я изменил jest.config.cjs файл, как показано ниже. По-прежнему нет результата.

 {
  ...
  testEnvironmentOptions: {
    NODE_OPTIONS: '--experimental-vm-modules',
  }
  ...
}
  

Наконец, обходной путь, который я нашел в файле pretter-plugin-xml package.json (который содержит import.meta.url в тестах), работает для меня.
Я перешел package.json:scripts:test с jest на явный jest.js позвоните:

 {
  ...
  "scripts": {
    ...
    "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
  },
  ...
}
  

Это похоже на настройку NODE_OPTIONS="--experimental-vm-modules" переменной среды, однако предоставленное решение кажется кроссплатформенным.


Мой tsconfig.json:compilerOptions фрагмент:

 {
  ...
  "module": "NodeNext",
  "target": "es2016",
  ...
}
  

The jest.config.cjs :

 /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
    preset: 'ts-jest',
    testEnvironment: 'node',

    // Without this option,
    // error `The 'import.meta' meta-property is only allowed when ...` reveals
    extensionsToTreatAsEsm: ['.ts'],

    // No import resolution, otherwise
    moduleNameMapper: {
        '^(\.{1,2}/.*)\.js$': '$1',
    },

    // Process infinitely run, if the option is not set
    transform: {
        '^. \.tsx?$': [
            'ts-jest',
            {
                useESM: true,
            },
        ],
    },
};