enzyme: ReactWrapper.unmount() throws Cannot render markup in a worker thread.

For some reasons, when I call unmount on a react wrapper node, React throws an error saying I don’t have a DOM.

jsdom is initialized, before importing React and enzyme, but I still get this error on unmount.

I even checked if the dom was really initialized properly, with the same check React does, and it’s evaluated as true so everything looks ok.

!!(
  typeof window !== 'undefined' &&
  window.document &&
  window.document.createElement
)

Any idea what could be the problem?

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 22 (3 by maintainers)

Most upvoted comments

import React from 'react';
import jsdom from 'jsdom';
import { mount } from 'enzyme';

const doc = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.document = doc;
global.window = doc.defaultView;

describe('foo', () => {
  it('works', () => {
    mount(<div>Foo Bar</div>);
  });

  it('doesnt work', () => {
    const mounted = mount(<div>Foo Bar</div>);
    mounted.unmount();
  });
});

stacktrace:

  1) foo doesnt work:
     Invariant Violation: dangerouslyReplaceNodeWithMarkup(...): Cannot render markup in a worker thread. Make sure `window` and `document` are available globally before requiring React when unit testing or use ReactDOMServer.renderToString() for server rendering.
      at invariant (node_modules/fbjs/lib/invariant.js:38:15)
      at Object.Danger.dangerouslyReplaceNodeWithMarkup (node_modules/react/lib/Danger.js:130:79)
      at Object.wrapper [as replaceNodeWithMarkup] (node_modules/react/lib/ReactPerf.js:66:21)
      at [object Object].ReactCompositeComponentMixin._replaceNodeWithMarkup (node_modules/react/lib/ReactCompositeComponent.js:679:31)
      at [object Object].ReactCompositeComponentMixin._updateRenderedComponent (node_modules/react/lib/ReactCompositeComponent.js:669:12)
      at [object Object].ReactCompositeComponentMixin._performComponentUpdate (node_modules/react/lib/ReactCompositeComponent.js:643:10)
      at [object Object].ReactCompositeComponentMixin.updateComponent (node_modules/react/lib/ReactCompositeComponent.js:572:12)
      at [object Object].wrapper [as updateComponent] (node_modules/react/lib/ReactPerf.js:66:21)
      at [object Object].ReactCompositeComponentMixin.performUpdateIfNecessary (node_modules/react/lib/ReactCompositeComponent.js:511:12)
      at Object.ReactReconciler.performUpdateIfNecessary (node_modules/react/lib/ReactReconciler.js:122:22)
      at runBatchedUpdates (node_modules/react/lib/ReactUpdates.js:143:21)
      at ReactReconcileTransaction.Mixin.perform (node_modules/react/lib/Transaction.js:136:20)
      at ReactUpdatesFlushTransaction.Mixin.perform (node_modules/react/lib/Transaction.js:136:20)
      at ReactUpdatesFlushTransaction._assign.perform (node_modules/react/lib/ReactUpdates.js:89:38)
      at Object.flushBatchedUpdates (node_modules/react/lib/ReactUpdates.js:165:19)
      at Object.wrapper [as flushBatchedUpdates] (node_modules/react/lib/ReactPerf.js:66:21)
      at ReactDefaultBatchingStrategyTransaction.Mixin.closeAll (node_modules/react/lib/Transaction.js:202:25)
      at ReactDefaultBatchingStrategyTransaction.Mixin.perform (node_modules/react/lib/Transaction.js:149:16)
      at Object.ReactDefaultBatchingStrategy.batchedUpdates (node_modules/react/lib/ReactDefaultBatchingStrategy.js:63:19)
      at Object.enqueueUpdate (node_modules/react/lib/ReactUpdates.js:194:22)
      at enqueueUpdate (node_modules/react/lib/ReactUpdateQueue.js:22:16)
      at Object.ReactUpdateQueue.enqueueSetState (node_modules/react/lib/ReactUpdateQueue.js:201:5)
      at [object Object].ReactComponent.setState (node_modules/react/lib/ReactComponent.js:67:16)
      at ReactWrapper.<anonymous> (node_modules/enzyme/build/ReactWrapper.js:215:28)
      at ReactWrapper.single (node_modules/enzyme/build/ReactWrapper.js:1297:19)
      at ReactWrapper.unmount (node_modules/enzyme/build/ReactWrapper.js:214:14)
      at Context.<anonymous> (foo.spec.js:11:1)

I agree with @j-funk, something appears to be wrong here. If nothing else, this seems like it ought to be addressed within the documentation?

I figured out what my issue was:

Here’s my setup-test-env.js file now:

/**
 * This is used to set up the environment that's needed for most
 * of the unit tests for the project which includes polyfilling,
 * chai setup, and initializing the DOM with jsdom
 */
import 'babel-polyfill'
import chai from 'chai'
import sinonChai from 'sinon-chai'
import {jsdom} from 'jsdom'

chai.use(sinonChai)

global.document = jsdom('<body></body>')
global.window = document.defaultView
global.navigator = window.navigator
global.expect = chai.expect

// this has to happen after the globals are set up because `chai-enzyme`
// will require `enzyme`, which requires `react`, which ultimately
// requires `fbjs/lib/ExecutionEnvironment` which (at require time) will
// attempt to determine the current environment (this is where it checks
// for whether the globals are present). Hence, the globals need to be
// initialized before requiring `chai-enzyme`.
chai.use(require('chai-enzyme')())

Sorry about the delay in posting a solution, here goes:

  • react-dom needs navigator to be a global (enzyme imports react-dom)
  • both react and enzyme imports need to happen after the globals are attached (so they need to be require calls, because imports happen before anything else irrespective of where they appear in the file
import jsdom from 'jsdom';

const doc = jsdom.jsdom('');
global.document = doc;
global.window = doc.defaultView;
global.navigator = {
  userAgent: 'node.js',
}

const React = require('react')
const mount = require('enzyme').mount

describe('foo', () => {
  it('works', () => {
    mount(<div>Foo Bar</div>);
  });

  it('doesnt work', () => {
    const mounted = mount(<div>Foo Bar</div>);
    mounted.unmount();
  });
});

I don’t think this issue should be closed.

To possibly help future debuggers.

The equivalent of the line below should also be placed after jsdom. const Adapter = require('enzyme-adapter-react-15');

That’s my scenario, I put the jsdom config on setup.js and just import the mount and React on the test files. So I don’t know why still happens.

In addition to @kentcdodds fixes, I managed to fix this issue by changing the order of my test-setup.js to the following:

/**
 * This is used to set up the environment that's needed for most
 * of the unit tests for the project which includes polyfilling,
 * chai setup, and initializing the DOM with jsdom
 */
import 'babel-polyfill'
import chai from 'chai'
import sinonChai from 'sinon-chai'
import {jsdom} from 'jsdom'

chai.use(sinonChai)

global.document = jsdom('<body></body>')
global.window = document.defaultView
global.navigator = window.navigator
global.expect = chai.expect

// this has to go after the window, document and navigator are set up
global.React = require('React');
global.TestUtils = require('react-addons-test-utils');

// this has to happen after the globals are set up because `chai-enzyme`
// will require `enzyme`, which requires `react`, which ultimately
// requires `fbjs/lib/ExecutionEnvironment` which (at require time) will
// attempt to determine the current environment (this is where it checks
// for whether the globals are present). Hence, the globals need to be
// initialized before requiring `chai-enzyme`.
chai.use(require('chai-enzyme')())