在基于jsdom的测试中调用setState会导致“无法在工作线程中呈现标记”的错误。

21

我正在使用jsdom在我的React组件下测试自己的微型"虚拟浏览器"工具,这个工具挺好用,但是当我尝试使用setState时就出问题了。例如,测试一个儿童年龄输入控件:

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);
    });
});

...在setState行上我得到:

未捕获的错误:不变式违规:dangerouslyRenderMarkup(...): 无法在工作线程中呈现标记。在单元测试时确保windowdocument在全局范围内可用,或使用React.renderToString进行服务器呈现。

我知道像TestBrowser这样基于jsdom的工具确实设置了windowdocument全局变量,如下所示:

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

我甚至尝试将setState包装成setTimeout(..., 0),但这也没有帮助。我该如何使测试状态更改有效?


1
看起来你是提到那个工具的作者。我没有看到它从任何地方派生出来。所以你应该明确说明这是你的工具。(这是SO政策。)此外,问题中似乎缺少一些关键信息:即,jsdom如何被初始化和被你的代码使用。把我们引导到一个外部网站并不能替代在问题本身中提供这些信息。 - Louis
我已经更新了文本。感谢您的反馈。 - Ivan Krechetov
4个回答

21

在加载时,React确定是否可以使用DOM,并将其存储为布尔值。

var canUseDOM = !!(
  typeof window !== 'undefined' &&
  window.document &&
  window.document.createElement
);
ExecutionEnvironment.canUseDOM = canUseDOM;
这意味着如果在这些条件成立之前加载,它会认为无法使用DOM。
您可以在beforeEach中进行monkey patch。
require('fbjs/lib/ExecutionEnvironment').canUseDOM = true

或者你可以一开始假装:

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

或者确保在设置JSDOM之前不要加载React,这是我唯一确定的方法,但也是最困难和容易出错的方法。

或者您可以将此报告为问题,或检查jest的源代码以查看它如何解决此问题。


1
执行环境似乎已经移动:const ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment'); - ehrencrona

10

如果您使用Mocha和Babel,则需要在编译器评估React代码之前设置jsdom。

// 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;

使用--require参数:

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

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

来源


你可以在 setup-jsdom.js 文件中添加 require('babel/register'); 并使用 mocha --compilers js:setup-jsdom.js 命令。 - senornestor
那个源链接对我非常有帮助 - 我消除了我之前使用的大概80%的垃圾模式。谢谢!建议在答案中将该链接更加明显 - 它很容易被忽略。 - killthrush
该源链接现在已经失效。 - Ryan Ore

3
我也曾在使用Babel测试ES6代码时遇到了这个错误。问题在于import React from 'react'const React = require('react')之间存在差异。如果您使用import语法,则所有的导入将在任何其他事情发生之前发生,因此您不能对windowdocument进行模拟。

因此,首先要在测试中(重要的地方)将import替换为require

第二个步骤是通过使用global.navigator = global.window.navigator来模拟global.navigator

希望这能帮助某些人。


1

1
虽然这理论上回答了问题,但最好在此处包含答案的基本部分,并提供参考链接。 - Baum mit Augen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接