Search code examples
node.jsreactjsjestjsmutation-observers

React testing: "TypeError: MutationObserver is not a constructor"


The Problem

I'm trying to test a new release of an internal component library, which recently upgraded some dependencies and now uses Jest 26 internally. That library also exports some testing helpers.

In another codebase that utilizes the aforementioned component library, I'm getting the following when running certain unit tests:

TypeError: MutationObserver is not a constructor

    at /<path_to_repo>/node_modules/@testing-library/dom/dist/wait-for.js:78:18

This may or may not be related to the fact that the testing helpers exported by the component library relied on Jest 26. (It's hard to tell if these helpers are being used -- we've got some very big "unit" tests.)

Relevant package versions (in the consuming codebase):

  • react-scripts 3.4.4
  • Jest 24.9.0
  • testing-library/dom 7.26.3
  • testing-library/jest-dom 5.10.0
  • testing-library/react ^10.0.0

Running yarn list jsdom yields the following:

├─ jest-environment-jsdom-fourteen@1.0.1
│  └─ jsdom@14.1.0
├─ jest-environment-jsdom@24.9.0
│  └─ jsdom@11.12.0
└─ jsdom@16.4.0

I know that upgrading react-scripts and Jest would likely fix this issue, but I'm trying to find a way that doesn't involve that. (We ideally don't want this to be a prerequisite for upgrading to our new component library version.)

Attempted Solutions

Forcing a Newer jsdom

First I tried adding

"resolutions": {
  "jsdom": "^14.0.0"
},

to my package.json. This did make yarn list jsdom output only jsdom@14.1.0, but I still got the same TypeError. Resolving to ^15.0.0 resulted in the same error, and resolving to ^16.0.0 gave me the following error:

TypeError: this.dom.runVMScript is not a function

  at JSDOMEnvironment.runScript (node_modules/jest-environment-jsdom/build/index.js:187:23)

Using jest-environment-jsdom-*

I tried following this thread by installing jest-environment-jsom-sixteen. Altering my test script in package.json and running yarn test --showConfig yields a bunch of info including the following line:

"testEnvironment": "/<path_to_repo>/node_modules/jest-environment-jsdom-sixteen/lib/index.js",

If I add a console.log(navigator.userAgent) to my test, I also get

Mozilla/5.0 (darwin) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/16.4.0

However, I still get the same TypeError: MutationObserver is not a constructor.

I also tried jest-environment-jsdom-fourteen and jest-environment-jsdom-fifteen, and tried while forcing jsdom to ^14.0.0. Same result.

Using mutationobserver-shim

I ran yarn add -D mutationobserver-shim and imported it (with import 'mutationobserver-shim') in my jestSetup.ts (which is what globalSetup in my Jest configuration points to), but that resulted in the following error:

ReferenceError: window is not defined
  at Object.<anonymous> (/<path_to_repo>/node_modules/mutationobserver-shim/dist/mutationobserver.min.js:12:1)

Adding the import instead to my test file yielded the same TypeError as above, and adding a console.log(global.MutationObserver) to my test file yielded undefined.

A Not-So-Great Solution

After a lot of trial and error, I discovered that if I 1) have jest-environment-jsdom-sixteen installed and tell Jest to use it and 2) add import 'mutationobserver-shim' in my test file, and the tests will pass. However, if I put add the import to my Jest globalSetup file, it doesn't work. I get the same ReferenceError: window is not defined error as before.

My Question

How can I get rid of both errors without requiring users of our component library to add an import statement to all of their test files?


Solution

  • A polyfill should be applied in setupFilesAfterEnv, because this is where the jsdom environment is instantiated and window becomes available. window isn't supposed to be available in globalSetup because it doesn't run in a test scope.

    In case this is an unejected create-react-app project, the setup file that corresponds to setupFilesAfterEnv is src/setupTests.ts (src/setupTests.js).

    That the newer jsdom version causes an error means it's incompatible with the old Jest version (24). They should be ejected and upgraded, or newer CRA (react-scripts@4) with Jest 26 support has to be used.