在 Jasmine 测试中更新 React 组件状态

Posted

技术标签:

【中文标题】在 Jasmine 测试中更新 React 组件状态【英文标题】:Updating React component state in Jasmine Test 【发布时间】:2014-04-23 04:15:03 【问题描述】:

我有一个相对简单的 React 组件,它根据其状态呈现一个列表。然后我有一个 karma/jasmine 测试来呈现组件,设置它的状态,并检查是否呈现了正确的标记。

我遇到的问题是,每次我在组件上执行 setState()forceUpdate() 时,都会出现错误:

TypeError: 'undefined' is not an object (evaluating 'deepestAncestor.firstChild')
        at /home/company/projects/user_interface_kit/bower_components/react/react.js:10314

在 React 组件中测试状态变化的正确方法是什么?

反应代码:

    var NotificationCenter = React.createClass(

        getInitialState: function()
            return notifications:[]
        ,


        render: function()             

                countContainerStyle = 
                    display: this.state.notifications.length > 0 ? '' : 'none'
                ;


            return (
                <div id="pc-notification-center">
                    <span className="pc-notification-center-bell" >
                        B
                    </span>
                    <span className="pc-notification-count-container" style=countContainerStyle>
                        <span className="pc-notification-count-circle">&#9679;</span>
                        <span className="pc-notification-count">this.state.notifications.length</span>
                    </span>
                </div>);
        

    );

    return NotificationCenter;
);

测试代码:

it('should set its notification count to the number of notifications it has', function() 
        var notificationCenter = NotificationCenter(),
            countNode;

        TestUtils.renderIntoDocument(notificationCenter);

        notificationCenter.setState(
            notifications: [1,2]
        );


        countNode = TestUtils.findRenderedDOMComponentWithClass(notificationCenter,'pc-notification-count');


        expect(countNode).toBe(2);
    );

编辑:完整堆栈跟踪

PhantomJS 1.9.7 (Linux) [object Object] [object Object] [object Object] should default its notification count to 0 FAILED
TypeError: 'undefined' is not an object (evaluating 'deepestAncestor.firstChild')
    at /home/company/projects/user_interface_kit/bower_components/react/react.js:10314
    at /home/company/projects/user_interface_kit/bower_components/react/react.js:10260
    at getNode (/home/company/projects/user_interface_kit/bower_components/react/react.js:9874)
    at /home/company/projects/user_interface_kit/bower_components/react/react.js:4472
    at /home/company/projects/user_interface_kit/.tmp/notification_center/popover.js:28
    at /home/company/projects/user_interface_kit/bower_components/react/react.js:5925
    at /home/company/projects/user_interface_kit/.tmp/notification_center/popover.js:75
    at /home/company/projects/user_interface_kit/bower_components/react/react.js:5925
    at /home/company/projects/user_interface_kit/.tmp/notification_center/popover.js:81
    at /home/company/projects/user_interface_kit/bower_components/react/react.js:10461
    at /home/company/projects/user_interface_kit/bower_components/react/react.js:11924
    at /home/company/projects/user_interface_kit/bower_components/react/react.js:13944
    at /home/company/projects/user_interface_kit/bower_components/react/react.js:13877
    at /home/company/projects/user_interface_kit/bower_components/react/react.js:4360
    at /home/company/projects/user_interface_kit/bower_components/react/react.js:10055
    at /home/company/projects/user_interface_kit/bower_components/react/react.js:11169
    at /home/company/projects/user_interface_kit/bower_components/react/react.js:10105
    at /home/company/projects/user_interface_kit/bower_components/react/react.js:11169
    at /home/company/projects/user_interface_kit/.tmp/notification_center/notification_center.js:50
    at /home/company/projects/user_interface_kit/bower_components/react/react.js:10461
    at /home/company/projects/user_interface_kit/bower_components/react/react.js:11924
    at /home/company/projects/user_interface_kit/bower_components/react/react.js:13944
    at /home/company/projects/user_interface_kit/bower_components/react/react.js:13877
    at /home/company/projects/user_interface_kit/bower_components/react/react.js:4360
    at /home/company/projects/user_interface_kit/bower_components/react/react-with-addons.js:10483
    at /home/company/projects/user_interface_kit/bower_components/react/react-with-addons.js:11597
    at /home/company/projects/user_interface_kit/bower_components/react/react-with-addons.js:10533
    at /home/company/projects/user_interface_kit/bower_components/react/react-with-addons.js:11597
    at /home/company/projects/user_interface_kit/bower_components/react/react-with-addons.js:12716
    at /home/company/projects/user_interface_kit/test/notification_center/notification_center_test.js:19
    at /home/company/projects/user_interface_kit/node_modules/karma-jasmine/lib/adapter.js:171
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1585
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:841
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1074
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:126
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1117
    at each (/home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:58)
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1118
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:895
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1074
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:126
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1117
    at each (/home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:58)
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1118
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:895
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1074
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:126
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1117
    at each (/home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:58)
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1118
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:895
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1074
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:126
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1117
    at each (/home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:58)
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1118
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:895
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1074
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:126
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1117
    at each (/home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:58)
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1118
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:895
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1074
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:126
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1117
    at each (/home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:58)
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1118
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:895
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1074
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:126
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1117
    at each (/home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:58)
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1118
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:895
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1074
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:126
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1117
    at each (/home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:58)
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1118
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:895
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1104
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:754
    at callGetModule (/home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1129)
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1479
    at /home/company/projects/user_interface_kit/node_modules/karma-requirejs/lib/require.js:1606

这是一个重现问题的小项目 https://github.com/treehau5/react_karma_requirejs_bug_reproduction

【问题讨论】:

你能发布更多的堆栈跟踪吗?测试的哪一行导致抛出异常? 一个最小的复制案例(在 jsbin 等中)会有所帮助;你不应该得到那个错误。随意在 react repo 上提交错误。 我添加了完整的堆栈跟踪。导致异常的行是 .setState()。如果我手动更新状态对象并在 forceUpdate 调用上调用 forceUpdate(),我会得到同样的错误。当组件尝试重新渲染自身时,似乎会发生此错误。 @BenAlpert 我无法轻松制作 jsbin/fiddle,但我创建了一个重现该问题的小项目。很简单github.com/treehau5/react_karma_requirejs_bug_reproduction 【参考方案1】:

这是 renderIntoDocument 源代码(我在项目中使用的 React 0.14.8): https://github.com/facebook/react/blob/v0.14.8/src/test/ReactTestUtils.js#L79

这是在 2 年前更改的时间:https://github.com/facebook/react/commit/ce95c3d042309d8aced894cc6be43d7e4cf96455

因此,尽管有名称,但它实际上并没有将任何内容呈现到文档中。

我还可以确认 Assaf 的答案仍然适用于 0.14.8 - 基本上,编写您自己的 renderIntoDocument。这对我有用:

function renderIntoDocument (instance) 
    var div = document.createElement('div');
    document.documentElement.appendChild(div);
    return ReactDOM.render(instance, div);

【讨论】:

有趣的是,它可能不会使 他们的 测试失败,但它确实让我的测试失败(因为我的代码期望 element.ownerDocument.body.contains(element) 为所有渲染元素返回 true)。【参考方案2】:

你仍然可以使用原来的TestUtils.renderIntoDocument,只需要将需要的React 移到beforeEach 中。因为如果你需要全局 React,每个测试的上下文都会不同,这会导致 2 个不同的实例挂载同一个组件。

beforeEach(function () 
    React = require('react/addons');
    TestUtils = React.addons.TestUtils;
);

【讨论】:

这个答案在调试了将近一周后才救了我:)【参考方案3】:

我不知道之前的答案是否仍然正确。

使用 React 0.11.1,这段代码可以正常工作:

var React = require('react/addons');
var TestUtils = React.addons.TestUtils;

jest.dontMock('public/components/MyThing.jsx');
var MyThing = require('public/components/MyThing.jsx');

describe('MyThing', function() 
  var html;

  describe('#render', function() 
    beforeEach(function()
      var component = MyThing();
      var componentInstance = TestUtils.renderIntoDocument(component);
      componentInstance.setState(isCool: true);

      html = componentInstance.getDOMNode().textContent;
    );

    it('includes something cool', function()
      expect(html).toContain('something cool');
    );
  );
);

【讨论】:

谁能验证这是否仍然适用于0.12.*【参考方案4】:

在深入研究了 React 和 React_with_addons 的工作原理之后,看起来测试中的本地 React 对象与 React_with_addons 使用的 React 实例不同。因此,如果您在测试中这样做:

react_with_addons.addons.TestUtils.renderIntoDocument(componentInstance);

然后 react_with_addon 的 React 实例注册 componentInstance 并初始化它的 nodeCache 对象。本地 React 实例保持不变。然后,如果您尝试像这样更新组件的状态:

componentInstance.updateState(key:'newValue');

React 的本地实例用于尝试更新 DOM。由于此实例从未挂载过任何组件,因此更新失败,您会收到“未定义”不是对象(正在评估“deepestAncestor.firstChild”)错误。

有趣的是,如果您将任何组件安装到本地 React 中,那么在更新组件的状态时您不会看到错误,即使该组件是使用 react_with_addon 的 React 对象安装的。

如果您的测试需要运行 setState,目前避免此问题的最佳方法是不使用 react_with_addon 的 renderIntoDocument 函数。相反,只需创建自己的。它只有两行:

 function renderIntoDocument(instance) 
        var div = document.createElement('div');
        return React.renderComponent(instance, div);
      ;

【讨论】:

这仍然是有效的黑客攻击吗?或者现在有什么好的解决方法? 我猜这不再有效了,因为自编写此答案以来,React 经历了许多变化。

以上是关于在 Jasmine 测试中更新 React 组件状态的主要内容,如果未能解决你的问题,请参考以下文章

Karma + Browserify + Jasmine + 伊斯坦布尔 + React 覆盖

如何用 Karma,Jasmine,Webpack 测试 UI 组件系列 配置篇

使用 Jasmine 间谍进行 Angular 2 组件测试“没有 Http 提供者!”错误

为使用 Observables 的 Angular 2 组件编写 Jasmine 测试

React - 在功能组件中测试外部功能

模拟指令以测试组件 - Angular 8 与 jasmine 和 Karma