Calling setState in jsdom-based tests causing "Cannot render markup in a worker thread" error
Asked Answered
M

4

21

I'm testing my React components under jsdom using my own tiny "virtual browser" utility. Works just fine, until I'm trying to setState. For example, when testing a children ages input control:

describe('rendering according to the draft value', function () {
    var component;

    beforeEach(function () {
        component = TestUtils.renderIntoDocument(
            React.createElement(ChildrenInput, {value: []})
        );

        component.setState({draft: [{age: null}, {age: null}]}, done);
    });

    it('overrides the value property for the count', function () {
        assert.strictEqual(component.refs.count.props.value, 2);
    });

    it('overrides the value property for the ages', function () {
        assert.strictEqual(component.refs.age1.props.value, null);
    });
});

…on the setState line I'm getting:

Uncaught Error: Invariant Violation: dangerouslyRenderMarkup(...): Cannot render markup in a worker thread. Make sure window and document are available globally before requiring React when unit testing or use React.renderToString for server rendering.

I know that window and document globals are indeed set by the jsdom-based TestBrowser, like that:

global.document = jsdom.jsdom('<html><body></body></html>', jsdom.level(1, 'core'));
global.window = global.document.parentWindow;

I even tried to wrap setState into a setTimeout(..., 0). It doesn't help. How can I make testing the state changes work?

Mikkel answered 11/11, 2014 at 14:46 Comment(1)
It looks like you are the author of that utility you mention. I don't see it being forked from anywhere. So you should mention that this is your tool. (This is SO policy.) Also, there seem to be some crucial information missing from the question: namely, how jsdom is being initialized and used by your code. Sending us to an external website is not a substitute for putting this information in the question itself.Chavez
S
21

At load time React determines if it can use DOM, and stores it as a boolean.

var canUseDOM = !!(
  typeof window !== 'undefined' &&
  window.document &&
  window.document.createElement
);
ExecutionEnvironment.canUseDOM = canUseDOM;

This means that if it's loaded before those conditions will be true, it assumes it can't use DOM.

You could monkey patch it in your beforeEach.

require('fbjs/lib/ExecutionEnvironment').canUseDOM = true

Or you could fake it at first:

global.window = {}; global.window.document = {createElement: function(){}};

Or ensure that you don't load React before you set up JSDOM, which is the only way I'm positive about, but it's also the most difficult and bug prone.

Or you could report this as an issue, or inspect jest's source and see how it resolves it.

Septillion answered 11/11, 2014 at 18:49 Comment(1)
ExecutionEnvironment seems to have moved: const ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');Brunabrunch
K
10

If you use Mocha and Babel, you will need to setup jsdom before the compilers evaluate the React code.

// setup-jsdom.js

var jsdom = require("jsdom");

// Setup the jsdom environment
// @see https://github.com/facebook/react/issues/5046
global.document = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.window = document.defaultView;
global.navigator = global.window.navigator;

With the --require arg:

$ mocha --compilers js:babel/register --require ./test/setup-jsdom.js test/*.js

See https://github.com/facebook/react/issues/5046#issuecomment-146222515

Source

Krystynakshatriya answered 7/10, 2015 at 15:30 Comment(3)
You can simply add require('babel/register'); to setup-jsdom.js and use mocha --compilers js:setup-jsdom.jsFairfield
That source link was hugely helpful for me - I eliminated probably 80% of the crap patterns I was using before. Thanks! Recommend making that link more visible in the answer - it's easy to miss.Foeticide
That source link is now broken.Weeks
B
3

I've also got this error using Babel for testing ES6 code. The point is that there's a difference between import React from 'react' and const React = require('react'). If you're using import syntax, then all your imports would happend before anything else, so you can't mock window or document before.

So first bit is to replace import by require in the tests(where it matters)

The second bit is to mock global.navigator by using global.navigator = global.window.navigator.

Hope it'll help somebody.

Bravar answered 9/7, 2015 at 13:40 Comment(0)
A
1

Nothing worked for me until I tried requiring jsdom-global in the mocha command:

--require jsdom-global/register

Source: https://github.com/rstacruz/jsdom-global/blob/master/README.md#mocha

Andi answered 16/1, 2017 at 0:37 Comment(1)
While this may theoretically answer the question, it would be preferable to include the essential parts of the answer here, and provide the link for reference.Agone

© 2022 - 2024 — McMap. All rights reserved.