19

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

JimmyTheCode
  • 3,783
  • 7
  • 29
  • 71
  • 1
    For information, this error may still happen if you're running Jest with `jsdom` as `testEnvironment`, see: https://github.com/jsdom/jsdom/issues/3363. With `node` as `testEnvironment` + Node 17, there should be no issue, as already stated – fservantdev Feb 14 '23 at 08:06

7 Answers7

19

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.

marcelocra
  • 2,094
  • 2
  • 24
  • 37
  • 3
    Never use the parse-stringify hack. Never. If you cannot use "structuredClone", just use the "cloneDeep" method of the "lodash" package. – mhombach Jan 04 '23 at 13:33
  • 1
    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. – marcelocra Jan 05 '23 at 02:46
  • 1
    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. – mhombach Jan 06 '23 at 12:16
  • 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. – marcelocra Jan 06 '23 at 15:22
4

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...

Steve Chambers
  • 37,270
  • 24
  • 156
  • 208
3

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

gbengaoyetade
  • 700
  • 8
  • 6
  • 3
    Just one consideration. You MUST have a valid JSON values other wise you have a error. Example when you have a date type in value – LuanLuanLuan Nov 01 '22 at 17:57
  • Never use the parse-stringify hack. Never. If you cannot use "structuredClone", just use the "cloneDeep" method of the "lodash" package. – mhombach Jan 04 '23 at 13:34
  • 7
    Why 'never' use? Seems like for specific, simple cases it works without issue. – JimmyTheCode Jan 05 '23 at 07:45
  • 2
    @JimmyTheCode 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. – mhombach Mar 06 '23 at 16:25
1

Had the same issue with jsdom. They have no structuredClone global in current (jsdom@22.1.0) 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"]
};

dhilt
  • 18,707
  • 8
  • 70
  • 85
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.

backslashN
  • 2,795
  • 3
  • 15
  • 25
0

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.

Sam Spade
  • 1,107
  • 7
  • 20
-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?

JimmyTheCode
  • 3,783
  • 7
  • 29
  • 71
  • 1
    Never use the parse-stringify hack. Never. If you cannot use "structuredClone", just use the "cloneDeep" method of the "lodash" package. – mhombach Jan 04 '23 at 13:33
  • 1
    For my use-case, this was absolutely fine. Please provide explanation for this. – JimmyTheCode Feb 01 '23 at 12:07
  • 2
    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. – mhombach Feb 02 '23 at 22:18
  • 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](https://github.com/jsdom/jsdom/issues/3363) with `jsdom` – fservantdev Feb 14 '23 at 08:11