53

I'm trying to write a simple jest test for a 3rd party package were using that only exports an ES module. It's a wrapper around an http server.

Here is a test repo I setup (just run yarn && yarn jest to reproduce): https://github.com/jamesopti/hocuspocus-testing

No matter what config I experiment with, I still get this error when trying to run it:

 Must use import to load ES Module: /Users/j/hocuspocus-testing/node_modules/@hocuspocus/server/dist/hocuspocus-server.esm.js

    > 1 | import { Server, Hocuspocus } from '@hocuspocus/server'
        | ^
      2 | import * as request from 'supertest'
      3 |
      4 | describe('Server (e2e)', () => {

Things I've tried already:

  • The Jest instructions on ES modules: https://jestjs.io/docs/ecmascript-modules

  • In Jest configuration using transformIgnorePatterns

    • transformIgnorePatterns: ['node_modules/(?!@hocuspocus/)']
  • Using Babel via babel-jest

    • modifying transform setup in Jest configuration as '^.+\.jsx?$': 'babel-jest', '^.+\.tsx?$': 'ts-jest'

    • Ran into the error You appear to be using a native ECMAScript module configuration file, which is only supported when running Babel asynchronously.

    • Using .babel.config.js instead of .babelrc.js

Any ideas what I'm missing here? I thought this would be straightforward

[EDIT 1] - Added tsconfig.json and a working src/index.ts file to the example repo.

1
  • I know it's years later, but have you tried escaping your / with \\/ ? Commented Nov 19, 2024 at 0:21

6 Answers 6

30

So for anyone still hitting this, ESM configuration explained in this section of documentation :

https://kulshekhar.github.io/ts-jest/docs/guides/esm-support

{
  // [...]
  "jest": {
    "extensionsToTreatAsEsm": [".ts"],
    "globals": {
      "ts-jest": {
        "useESM": true
      }
    }
  }
}
Sign up to request clarification or add additional context in comments.

2 Comments

You may also want to add ` "moduleNameMapper": { "^(\\.{1,2}/.*)\\.js$": "$1" }` as well as noted in kulshekhar.github.io/ts-jest/docs/guides/esm-support
As of Jan 2023 there's a new syntax by @BigMan73 - stackoverflow.com/a/75001235/122441
19

JAN 2023: ES2022, TypeScript 4.9.4, jest 29.3.1, ts-jest 29.0.3 FEB 2024: Modified transform file regex based on comment from @vick

This is what worked for me, after 2 hours of frustration. I used this configuration in jest.config.ts:

import type { JestConfigWithTsJest } from 'ts-jest'

const config: JestConfigWithTsJest = {
  extensionsToTreatAsEsm: ['.ts'],
  verbose: true,
  preset: 'ts-jest/presets/default-esm',
  testEnvironment: 'node',
  transform: {
    '^.+\\.tsx?$': ['ts-jest', { useESM: true }]
  },
  testPathIgnorePatterns: ['./dist']
}

export default config

Change the test script in package.json to:

I use pnpm. Change to npx jest with npm, or yarn exec with yarn

...
"scripts": {
  ...
  "test": "NODE_OPTIONS=--experimental-vm-modules pnpm exec jest",
  ...
}
...

tsconfig.json:

{
  "compilerOptions": {
    "rootDirs": ["src"],
    "outDir": "dist",
    "lib": ["ES2022"],
    "target": "ES2022",
    "module": "ES2022",
    "composite": true,
    "moduleResolution": "node",
    "declaration": true,
    "declarationMap": true,
    "incremental": true,
    "esModuleInterop": true,
    "types": ["jest", "node", "@types/jest"],
    "sourceMap": true
  },
  "ts-node": {
    "esm": true,
    "experimentalSpecifierResolution": "node"
  },
  "include": ["./src/**/*", "./tests/**/*"]
}

See this (rather confusing) documentation for reference: https://kulshekhar.github.io/ts-jest/docs/guides/esm-support/

3 Comments

What about passing args to Jest, like specifying a config file? When I do "itest-local": "NODE_OPTIONS=--experimental-vm-modules yarn exec jest -c jest.config.integration.ts --runInBand", the config doesn't get picked up.
I just tried with this command (I'm using pnpm not yarn) and it worked fine with a custom jest config file. I intentionally caused damage to the file (used a testEnvironment: 'nodeXX', which doesn't exist), in order to test that the config file is actually being read. "test": "NODE_OPTIONS=--experimental-vm-modules pnpm exec jest --config local.jest.config.ts"
Your transform regex, '^.+\\.(ts|tsx)?$' is not quite right. As it is it will match any file ending with a literal . in addition to ones ending in .ts and .tsx. The simplest regex here is "\\.tsx?$" which matches what I think you intended, files ending in .ts or .tsx.
18

Much as I'm loathe to give a "use something else" type answer, I'm going to, even though it may not directly answer the original question.

It does, however, answer the question "How can I make it easy to test my TypeScript files which have ESM dependencies, whilst using Jest syntax?"

After messing around with various jest.config.js options, with ideas taken from some of these answers, I eventually just did:

npm install -D vitest       

... and, it all just worked. No jest.config.js file. No messing with any other dependencies.

The only change I had to make was to import some test-related functions that you don't have to with Jest:

import { expect, describe, it } from "vitest";

Other than this, it's easy to forget I'm not simply using Jest.

Further detail here: https://vitest.dev/guide/.

EDIT: As suggested in a helpful comment, you can set an option such that importing expect / describe / it is not necessary.

See here for details on how to use globals: true: https://vitest.dev/config/#globals.

4 Comments

🙏 I spent damn near an hour trying to get Jest to work; took 2 mins to get this working.
Btw if you define globals: true in the vitest.config.js file then you don't have to import expect, describe, it or any other globals, see: vitest.dev/config/#globals
I also added yarn add -D @types/jest to cover the global types, seems to work fine
You don't need @types/jest, you can just set { "compilerOptions": { "types": ["vitest/globals"] } } in tsconfig.json
2

You don't have a tsconfig.json file that specifies module. Therefore it uses the default, where it transpiles all your modules to CommonJS syntax, which uses require.

If you actually look at your dist/hocuspocus-server.esm.js, you should see it using require over the ESM import syntax.

3 Comments

Thanks for catching that! I forgot to copy over the tsconfig.json from the real project (just pushed an update). I still get the same error though.
re: dist/hocuspocus-server.esm.js, the package is not using require, its all ES module import/exports. Is that an issue on the publisher's side?
Perhaps since your tsconfig.json only includes src/, ts-node (which iirc is what jest uses) might still compile your test/ to CommonJS. I don't know the exact interaction there, but I still feel like it's an issue with the test code being transpiled to use require.
1

I was having the same problem with my svelte app and testing. I ultimately traced it to having a jest.config.js and a jest.config.json in my root folder. It seems that jest does not have automatic config file resolution and was using a default configuration instead of either of my specified configurations.

Comments

0

For what it's worth, the OPs approach of using transformIgnorePatterns works fine for me - just need to escape the forward slashes.

  transformIgnorePatterns: ['node_modules\\/(?!use-local-storage-state\\/)'],

The context this is for is that the package I'm using has code compiled to ESM only, which won't work for jest, and won't be transpiled for CJS because it lives in node_modules.

ie. without the above config, I get this error:

    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import useLocalStorageState from './src/useLocalStorageState.js';
                                                                                      ^^^^^^

    SyntaxError: Cannot use import statement outside a module

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.