"ReferenceError: structuredClone is not defined" using jest with nodejs & typescript
Asked Answered
O

8

34

I'm running tests with jest in a simple NodeJS app that uses typescript. My test is throwing an error: ReferenceError: structuredClone is not defined.

I'm not getting any linter errors and the code compiles fine normally.

  const variableForValidation = structuredClone(variableForValidationUncloned);

package.json:

  "dependencies": {
    ...
  },
  "devDependencies": {
    "@types/jest": "^29.0.0",
    "@types/node": "^18.7.15",
    "@typescript-eslint/eslint-plugin": "^5.36.1",
    "@typescript-eslint/parser": "^5.36.1",
    "eslint": "^8.23.0",
    "jest": "^28.0.1",
    "nodemon": "^2.0.19",
    "serverless-plugin-typescript": "^2.1.2",
    "ts-jest": "^28.0.8",
    "ts-node": "^10.9.1",
    "typescript": "^4.8.2"
  }

This github issue suggests to me that the issue has been resolved: https://github.com/facebook/jest/issues/12628 - or perhaps I'm misunderstanding?

I've seen a similar Stack question but using Mocha: mocha not recognizing structuredClone is not defined

Orola answered 5/9, 2022 at 9:42 Comment(1)
For information, this error may still happen if you're running Jest with jsdom as testEnvironment, see: github.com/jsdom/jsdom/issues/3363. With node as testEnvironment + Node 17, there should be no issue, as already statedCameraman
C
30

structuredClone was added in Node 17.

If you can't update, the JSON hack (stringify then parse) works, but has some shortcomings that might be relevant to you:

  • Recursive data structures: JSON.stringify() will throw when you give it a recursive data structure. This can happen quite easily when working with linked lists or trees.
  • Built-in types: JSON.stringify() will throw if the value contains other JS built-ins like Map, Set, Date, RegExp or ArrayBuffer.
  • Functions: JSON.stringify() will quietly discard functions.

Edit:

I recently learned about the json-stringify-safe, that helps with the circular issue.

Calcine answered 13/9, 2022 at 14:19 Comment(4)
Never use the parse-stringify hack. Never. If you cannot use "structuredClone", just use the "cloneDeep" method of the "lodash" package.Hourglass
Why do you prefer cloneDeep over parse-stringify? Pulling an external lib is not always something people are willing to do.. at least not without a good reason to.Calcine
It's a "hack" and workaround which has many many downsides while there is no pro-argument. You can create a util-function for deepcloning yourself or use the more stable lodash-version (which you can use without importing the whole lib). That beeing said, the "structuredClone" is natively existing in ALL browsers since march 2022. So just use that, or, if you are having clients that are years old and you need to consider, use well-used polyfill-libraries which solve the problem for you.Hourglass
Well, that there are downsides we know... that exactly what I mention in my answer. It would be interesting to understand which ones you are referring to, if not the ones I already raised. The clear pro-argument is that parse-stringify is everywhere and is very simple to use.. if the downsides are not a problem to a person, I don't see why they shouldn't use it.Calcine
C
8

For the same error, having the code below in the test file fixed the issue for me.

global.structuredClone = (val) => JSON.parse(JSON.stringify(val))

Reference

Chandelle answered 12/9, 2022 at 22:0 Comment(5)
Just one consideration. You MUST have a valid JSON values other wise you have a error. Example when you have a date type in valueVolnay
Never use the parse-stringify hack. Never. If you cannot use "structuredClone", just use the "cloneDeep" method of the "lodash" package.Hourglass
Why 'never' use? Seems like for specific, simple cases it works without issue.Orola
@Orola It doesn't work for enums, functions, symbols, circular structures, maps, sets, all existing class-instanciated objects and there might be even more in that list. JS has an old native method to clone an object, just not in a nested way. If you need to rely on outdated tech like that, code a util function that uses that old native cloning function, travel down the nesting and clone everything. And I'm not even mentioning the bad clean-code aspect because of the fully wrong semantic of introducing "JSON" for when it has absolutely nothing to do with the code and is irrelevant.Hourglass
JSON.stringify hack does not make a deep cloneEntoderm
T
6

I had the same issue with testEnvironment set to jest-environment-jsdom in my Jest config file.

@fservantdev has linked to this issue (currently open at the time of writing) regarding this: https://github.com/jsdom/jsdom/issues/3363

There are several possible workarounds suggested in that thread - the following worked for me:

  1. npm install @ungap/structured-clone
  2. npm i --save-dev @types/ungap__structured-clone
  3. Add the following line at the top of all the files where structuredClone is being used: import structuredClone from "@ungap/structured-clone";.

UPDATE: This suggestion from tkrotoff might be a better one. In short, this is a hot topic at the time of writing so worth reading the thread to stay up to date...

Travers answered 17/3, 2023 at 15:54 Comment(0)
K
3

Had the same issue with jsdom. They have no structuredClone global in current ([email protected]) version, and the issue seems stale: https://github.com/jsdom/jsdom/issues/3363.

In addition to the workarounds discussed, I would provide my approach based on setupFiles jest setting:

// global.mock.js
global.structuredClone = v => JSON.parse(JSON.stringify(v));
// jest.config.js
module.exports = {
  testEnvironment: "jsdom",
  ...
  roots: ["./__tests__"],
  setupFiles: ["<rootDir>/global.mock.js"]
};

Kor answered 20/7, 2023 at 11:7 Comment(0)
O
2

For anyone who has already updated Jest and Node and still can't get structuredClone to work, try running:

console.log(`Node Version: ${process.version}`);

And verify that the logged version number supports structuredClone.

IDE misconfigurations can cause Jest to be run using a different version of Node. When I ran my project it used Node v17, but when I ran tests my IDE decided to use Node v16. Took hours to figure out what was happening.


After running into this problem again, jest 27 combined with ts-jest 27 seems to not be able to use structuredClone even if you are on Node v17. I solved this by updating to jest 29 and ts-jest 29.

Osteopath answered 25/5, 2023 at 2:21 Comment(0)
X
0

A polyfill of structuredClone is available in core-js. Add it to your jest.config.js.

import 'core-js' or require('core-js')

Note: Don't forget to add core-js to your babel exclude pattern.

Xyloid answered 3/4, 2023 at 6:13 Comment(1)
Could you please provide an example? I am struggling with this right now.Eustasius
F
0

Update the version of your node to the most recent one.

Fazeli answered 28/2 at 5:42 Comment(0)
O
-1

I couldn't figure it out, so I set my own global:

// globals.ts
if(!global.structuredClone){
    global.structuredClone = function structuredClone(objectToClone: any) {
          const stringified = JSON.stringify(objectToClone);
          const parsed = JSON.parse(stringified);
          return parsed;
        }
}
// entry point of app, eg index.ts:
import './globals.ts'
// ...

I think it might be because the target in my tsconfig is converting my typescript files to a javascript/node version before structuredClone was added?

Orola answered 8/9, 2022 at 6:20 Comment(4)
Never use the parse-stringify hack. Never. If you cannot use "structuredClone", just use the "cloneDeep" method of the "lodash" package.Hourglass
For my use-case, this was absolutely fine. Please provide explanation for this.Orola
Sure: It doesn't work for enums, functions, symbols, circular structures, maps, sets, all existing class-instanciated objects and there might be even more in that list. JS has an old native method to clone an object, just not in a nested way. If you need to rely on outdated tech like that, code a util function that uses that old native cloning function, travel down the nesting and clone everything. And I'm not even mentioning the bad clean-code aspect because of the fully wrong semantic of introducing "JSON" for when it has absolutely nothing to do with the code and is irrelevant.Hourglass
If you still have the issue with Node 17, you may have to check the testEnvironment in your Jest config. There is a known issue with jsdomCameraman

© 2022 - 2024 — McMap. All rights reserved.