Enzyme: setState causes Invariant Violation when it changes markup

Created on 7 Dec 2015  路  9Comments  路  Source: enzymejs/enzyme

I'm aware of #27, mine happens on React v0.14 and it crushes my soul as well :disappointed:

As far as I've tested (AFAIT?), this only happens when the state alters markup:

import React from 'react';
import { mount, describeWithDOM } from 'enzyme';

const Component = React.createClass({
  getInitialState() {
    return { flag: false };
  },

  render() {
    return this.state.flag ? <a /> : <div />
  }
});

describeWithDOM('<Component />', () => {
  it('turns on the flag', () => {
    const wrapper = mount(<Component />);
    wrapper.setState({ flag: true });
  });
});
Error: 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.

This does not happen if both elements are the same (e.g. this.state.flag ? <div /> : <div />).

Jest seemed to have solved this issue, it doesn't happen there, so it might be worth looking how they did it. I can take a look later, but I was wondering if you knew what's causing this.

This SO answer might help, though I'm not sure if it's related.

mount bug

Most helpful comment

Polluting the global document causes side effects among tests, they basically share the DOM thus causing nasty behaviors when running multiple tests with Mocha for example. So that workaround fixes one problem but causes others

All 9 comments

Hmm. Surprised this is happening. I will look into it as soon as I get a sec.

@silvenon I'm looking into this but it looks like React 0.14 has a hard dependency on document.createElement at require-time in order to get this to work properly, where-as React 0.13 did not. There may be a way around this, but I'm not sure what it is yet.

In the mean time, you should be able to get this working by requiring jsdom and leak it to the global object _before_ requiring enzyme (which is what jest is doing, FWIW).

global.document = require('jsdom').jsdom('');
var { mount } = require('enzyme');

// use `mount` here like normal, but don't use `describeWithDOM`...

note that the usage of require over import here is intentional, since the first statement must be executed before enzyme (and thus react) get's required.

I'm working on a less brittle way to fix this...

I managed to keep my precious imports by adding this helper:

// test/global.js
import jsdom from 'jsdom';

global.document = jsdom.jsdom('');
global.window = document.defaultView;
global.navigator = window.navigator;

And passing --require ./test/global to Mocha, which ensured that it will be loaded first.

I also had to define window and navigator. navigator because react-dom was calling navigator.userAgent.indexOf(), which was throwing.

Thanks, it works now :smiley:

I realized that my solution only initializes the document once, is this bad?

@silvenon it depends. Doing so can simplify a lot of things. some libraries will cache a reference to the document at require time (like jquery) and thus you have to jump through a lot of hoops to have code work when using a new document for every test. We plan on using only a single document for our entire test suite at airbnb soon, but we don't yet.

As far as react goes, enzyme's mount() uses TestUtils.renderIntoDocument internally which doesn't even attach itself to the main document, so it has pretty limited side effects that could affect other tests, but you still need to be conscious about some DOM APIs that will have side effects across tests.

Also, you should be able to refresh the document later on in your tests and react will work just fine with it. React doesn't cache any document references AFAICT, it just uses whatever document is off the global.

Thanks for the detailed explanation :+1: Learning so much from you guys.

Closing this issue as it should now be "fixed". This problem should no longer exist so long as you have a global document object prior to requiring react for the first time.

Polluting the global document causes side effects among tests, they basically share the DOM thus causing nasty behaviors when running multiple tests with Mocha for example. So that workaround fixes one problem but causes others

Hey all - just for reference, requiring jsdom prior to requiring enzyme fixed this issue for me as well. Thanks to all for the assistance here.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

timhonders picture timhonders  路  3Comments

amcmillan01 picture amcmillan01  路  3Comments

andrewhl picture andrewhl  路  3Comments

blainekasten picture blainekasten  路  3Comments

abe903 picture abe903  路  3Comments