Search code examples
typescriptjestjses6-modulests-jest

How to handle relative imports in Jest with ESM modules written in TypeScript?


I am using Jest for my unit tests. My source code is written in TypeScript and compile to ESM output by setting tsconfig.json property module to ES2020. Additionally, I am using ts-jest as a transformer to convert the code on the fly.

The setup is working fine as long as I don't import relative files in ESM modules. As per node.js ESM documentation, the extension is mandatory when specifying import source.

Since it is clear from various threads that typescript doesn't rewrite import paths or add any extension to the compiled JS code, I manually add extension to the import statements like like this:

// TS CODE
import { catalog } from './catalog.js';

// Compiled ESM2020 target and module
import { catalog } from './catalog.js';

The the main problem is that when ts-jest is transpiling the code on the fly, this file catalog.js simply doesn't exist in src folder. Instead original file with .ts extension as catalog.ts exists! And, jest complains that this file doesn't exist which is right:

Cannot find module './catalog.js' from 'src/index.ts'

So, how do I get around this problem?

On a side note, I need to stick to ESM output since I am using many ESM only packages and TypeScript compile doesn't retain dynamic import statements. It converts those to require statements. This is already controversial discussion as CommonJS supports dynamic imports.

For reference, this is my jest.config.js file:

/** @type {import('jest').Config} */
const config = {
  // Indicates whether the coverage information should be collected while executing the test
  collectCoverage: true,

  // A preset that is used as a base for Jest's configuration
  preset: 'ts-jest/presets/default-esm-legacy',

  // The paths to modules that run some code to configure or set up the testing environment before each test
  setupFiles: ['./src/jest-setup.ts'],

  // The test environment that will be used for testing
  testEnvironment: 'node',

  // The glob patterns Jest uses to detect test files
  testMatch: ['**/__tests__/**/*.[t]s?(x)', '**/?(*.)+(spec|test).[t]s?(x)'],

  // A map from regular expressions to paths to transformers
  transform: {
    '^.+\\.tsx?$': [
      'ts-jest',
      {
        useESM: true,
      },
    ],
  }
};

export default config;

Solution

  • In the section "Use ESM presets" of the ESM support part of the ts-jest documentation, the example configuration contains the following part, which is missing in your configuration:

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