55

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

2

11 Answers 11

48

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 2fev23:

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

Edit 24jan25:

There are other options, sorted by GitHub stars:

Sign up to request clarification or add additional context in comments.

6 Comments

Never use the parse-stringify hack. Never. If you cannot use "structuredClone", just use the "cloneDeep" method of the "lodash" package.
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.
If people were using a well supported, multi-functional library instead of another micro-dependency for each small problem they encounter, we probably wouldn't be as deep in the supply-chain-attack-mess as we are now... (just my 5 cents)
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.
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.
Solution is to use node >= 17, e.g. nvm use 20. Upvoted for the explanation why the error happens.
15

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

5 Comments

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
Never use the parse-stringify hack. Never. If you cannot use "structuredClone", just use the "cloneDeep" method of the "lodash" package.
Why 'never' use? Seems like for specific, simple cases it works without issue.
@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.
JSON.stringify hack does not make a deep clone
8

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

Comments

6

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"]
};

Comments

4

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.

1 Comment

I have node v20, jest v29, and ts-jest v29, but still getting this issue. Based off some of the other issues this might be related to JSDOM
3

Solution with Jest

global.structuredClone = jest.fn((val): unknown =>
  JSON.parse(JSON.stringify(val)),
);

1 Comment

Careful. This is a work-around and not equivalent to structuredNode.
2

Because of this problem I switched from jest to vitest (it's jest compatible). The migration was really quite easy and now everything is faster and leaner with less configuration. structuredClone was also onboard.

npm remove jest identity-obj-proxy ts-jest jest-environment-jsdom @types/jest
npm install -D vitest happy-dom
rm jest.config.js

add or modify your vite.config.js and add these lines

export default defineConfig({
test: {                                                                                                                                                                                            
        setupFiles: './jest.setup.ts',                                                                                                                                               
        environment: 'happy-dom',                                                                                                                                                                  
},
})

And add this first line at every *.test.ts-file:

import {describe, test, expect} from 'vitest'

Comments

1

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.

1 Comment

Could you please provide an example? I am struggling with this right now.
1

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

Comments

0

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?

4 Comments

Never use the parse-stringify hack. Never. If you cannot use "structuredClone", just use the "cloneDeep" method of the "lodash" package.
For my use-case, this was absolutely fine. Please provide explanation for this.
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.
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 jsdom
0

structuredClone was added in Node.js 17. So, update your Node.js to the latest version, and the issue will be fixed.

1 Comment

This doesn't seem to resolve the issue

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.