Can't use uuid module when running tests with Jest

Viewed 2263

I have a very simple Node.js (12.16.3) application that uses Express 4.17.1. I'm trying to use Jest 26.0.1 to run the test suite, but the same is failing due to some issue with the uuid module (version 8.1.0) used across the entire project:

[x80486@uplink:~/Workshop/node-guacamole]$ npm run test 

> node-guacamole@0.3.0 test /home/x80486/Workshop/node-guacamole
> node --experimental-modules --experimental-vm-modules ./node_modules/.bin/jest --coverage --detectOpenHandles --forceExit --verbose

(node:71155) ExperimentalWarning: The ESM module loader is experimental.
 FAIL  src/domain/customer.test.js
  ● Test suite failed to run

    SyntaxError: The requested module 'uuid' does not provide an export named 'v4'

      at jasmine2 (node_modules/jest-jasmine2/build/index.js:228:5)

 FAIL  src/service/customer.service.test.js
  ● Test suite failed to run

    SyntaxError: The requested module 'uuid' does not provide an export named 'v4'

          at async Promise.all (index 4)
      at jasmine2 (node_modules/jest-jasmine2/build/index.js:228:5)

 FAIL  src/handler/customer.handler.test.js
  ● Test suite failed to run

    SyntaxError: The requested module 'uuid' does not provide an export named 'v4'

          at async Promise.all (index 2)
          at async Promise.all (index 7)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |       0 |        0 |       0 |       0 |                   
----------|---------|----------|---------|---------|-------------------
Test Suites: 3 failed, 3 total
Tests:       0 total
Snapshots:   0 total
Time:        0.63 s
Ran all test suites.

I'm importing the module like: import { v4 } from "uuid"; and on the other hand, the application runs successfully:

[x80486@uplink:~/Workshop/node-guacamole]$ npm run start:dev 

> node-guacamole@0.3.0 start:dev /home/x80486/Workshop/node-guacamole
> nodemon --experimental-modules --experimental-vm-modules ./src/main.js

[nodemon] 2.0.4
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node --experimental-modules --experimental-vm-modules ./src/main.js`
(node:74672) ExperimentalWarning: The ESM module loader is experimental.
2020-06-03T03:28:48.889Z [debug] - Server running at http://localhost:8080
2020-06-03T03:28:48.889Z [info] - Press CTRL-C to stop

...and everything works fine. I'm puzzled... I don't understand why this fails with Jest only. Is there something else I need to do to make it work?

3 Answers

TL;DR: Jest not yet supports the field "exports" in package.json.

The problem is that Node.js uses the ESM version since it understands the "exports" field in the package.json, but since Jest does not yet support it, Jest uses the "main" field in package.json which exports the CommonJS version. See the relevant package.json section:

...
"main": "./dist/index.js",
"exports": {
  "./package.json": "./package.json",
  ".": {
    "require": "./dist/index.js",
    "import": "./wrapper.mjs"
  }
},
...

What this does it:

  1. The default export is main, as always.
  2. If the exports is understood, that overwrites the "main" exports
  3. The default exports "." defines a require and import, so Node.js uses the "import"
  4. But the problem is that the Jest issue indicates that it does not yet understand "Package Exports", so Jest is trying to load the CommonJS file (from "main") while Node.js loads the ESM file.

I found the exact same issue and documented it here. As an experiment, this should work with Jest:

import uuid from 'uuid';
const { v4 } = uuid;

But that will not work with Node.js since UUID does not define a default export.

You have two realistic options:

  • Wait until Jest supports package exports, they are doing a great job with ESM support and I don't think it will take long.
  • Upvote my proposal in the uuid package that they also export a default, like export default uuid in wrapper.mjs, which would allow you to use the snippet I put above.

The problem is version incompatibility between Jest and uuid. uuid version 8 supports native ES modules while Jest version 26 doesn't.

You can always uuid version 3.x.x with the following syntax:

const uuid = require('uuid/v4');
uuid(); // ⇨ '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'

My answer is a side note, but I suspect the nature of the issue has to do with the import syntax of import { v4 } from "uuid".

For example there is a related warning there:

(node:74672) ExperimentalWarning: The ESM module loader is experimental.

There may be a subtle incongruence between your normally-bundled JavaScript and the way the modules are loaded into the Jest system. Therefore, a solution might involve an addition into a config file--so Jest can understand ESM syntax. For example here is an interesting answer: ts-jest does not recognize es6 imports

Related MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import

The import statement cannot be used in embedded scripts unless such script has a type="module". Bindings imported are called live bindings because they are updated by the module that exported the binding.

As a test, you could see if it makes a difference importing it like this:

const { v4: uuidv4 } = require('uuid');
const v4 = uuidv4;

or

const v4 = require('uuid').v4 // might work as well

I wouldn't recommend using that if it works, but it should narrow it down to definitely that syntax.

The issue stems from this file here: https://github.com/uuidjs/uuid/blob/master/src/index.js in my opinion, because the file is a pass-through (roughly analogous to a symlink), so Jest is trying to import an export of an export.

It's probably related to Babel and a lack of transformation fixtures. Fixing it properly should alleviate the whole class of issues related to "ESM loader".

Related