【问题标题】:How to test a prop update on React component如何测试 React 组件的 prop 更新
【发布时间】:2015-08-17 07:55:23
【问题描述】:

单元测试 React 组件 prop 更新的正确方法是什么。

这是我的测试夹具;

describe('updating the value', function(){
        var component;
        beforeEach(function(){
            component = TestUtils.renderIntoDocument(<MyComponent value={true} />);
        });

        it('should update the state of the component when the value prop is changed', function(){
            // Act
            component.props.value = false;
            component.forceUpdate();
            // Assert
            expect(component.state.value).toBe(false);
        });
});

这工作正常并且测试通过,但是这会显示一个反应警告消息

'Warning: Dont set .props.value of the React component <exports />. Instead specify the correct value when initially creating the element or use React.cloneElement to make a new element with updated props.'

我只想测试属性的更新,而不是创建具有不同属性的元素的新实例。有没有更好的方法来进行此属性更新?

【问题讨论】:

    标签: unit-testing jasmine reactjs


    【解决方案1】:

    AirBnB 的 Enzyme 库为这个问题提供了一个优雅的解决方案。

    它提供了一个 setProps 方法,可以在浅层或 jsdom 包装器上调用。

        it("Component should call componentWillReceiveProps on update", () => {
            const spy = sinon.spy(Component.prototype, "componentWillReceiveProps");
            const wrapper = shallow(<Component {...props} />);
    
            expect(spy.calledOnce).to.equal(false);
            wrapper.setProps({ prop: 2 });
            expect(spy.calledOnce).to.equal(true);
        });
    

    【讨论】:

    • 但是 OP 没有提到 Enzyme,也没有在他们的标签中提到
    • 如果我们已经挂载了组件呢?
    • 我不建议在 '21 中使用这种方法进行测试,尽管我不知道具体你在测试什么我会专注于断言 prop 更改的应用程序在组件输出中正确呈现而不是道具本身的价值。
    【解决方案2】:

    如果你在同一个容器节点中使用不同的 props 重新渲染元素,它将被更新而不是重新挂载。见React.render

    在您的情况下,您应该直接使用ReactDOM.render 而不是TestUtils.renderIntoDocument。后来的creates a new container node every time it is called,因此也是一个新组件。

    var node, component;
    beforeEach(function(){
        node = document.createElement('div');
        component = ReactDOM.render(<MyComponent value={true} />, node);
    });
    
    it('should update the state of the component when the value prop is changed', function(){
        // `component` will be updated instead of remounted
        ReactDOM.render(<MyComponent value={false} />, node);
        // Assert that `component` has updated its state in response to a prop change
        expect(component.state.value).toBe(false);
    });
    

    【讨论】:

    • 在实际实践中,并不是所有情况下都显式调用render。更好的方法是使用状态数据更改来触发重新渲染,因为在实时环境中可能存在一些其他钩子,而您通过直接调用状态更改可能会错过这些钩子。
    • 如果您正在测试对 componentWillReceivePropscomponentDidUpdate 等属性更改的响应,我也会这样做。也就是说,如果可能的话,我会尝试重写组件,以便在渲染时动态计算 state 中的值(而不是使用 state)。
    • +1 为答案。请注意,现在是 ReactDOM.render 而不仅仅是 React.render(我认为是 0.14 版)。
    • TestUtils.renderIntoDocumentReactDOM.render 都使用来自 ReactDOM.render 的返回值。根据React docs:“使用这个返回值是遗留的,应该避免,因为未来版本的 React 在某些情况下可能会异步渲染组件”。有没有更好的解决方案可以避免使用 ReactDOM.render 输出?
    • 如果MyComponent 包裹在Provider 周围会怎样?
    【解决方案3】:

    警告:这实际上不会改变道具。

    但对我来说,我只想在componentWillReceiveProps 中测试我的逻辑。所以我直接打电话给myComponent.componentWillReceiveProps(/*new props*/)

    我不需要/不想测试 React 在 props 更改时调用该方法,或者 React 在 props 更改时设置 props,只是如果 props 与传入的内容不同,则会触发一些动画。

    【讨论】:

      【解决方案4】:

      快速添加,因为我正在寻找 testing-library 的答案,但在这里没有找到:this issue 中有一个示例,它看起来像这样:

      const {container} = render(<Foo bar={true} />)
      
      // update the props, re-render to the same container
      render(<Foo bar={false} />, {container})
      

      另外,testing-library 现在提供了一个rerender method 来完成同样的事情。

      【讨论】:

        【解决方案5】:

        这是一个较老的问题,但万一其他人偶然发现了这个问题,以下设置对我很有效:

        it('updates component on property update', () => {
            let TestParent = React.createClass({
                getInitialState() {
                    return {value: true};
                },
                render() {
                    return <MyComponent value={this.state.value}/>;
                }
            });
            component = TestUtils.renderIntoDocument(<TestParent/>);
            component.setState({value: false});
            // Verification code follows
        });
        

        这使得 React 运行通常的组件更新。

        【讨论】:

          【解决方案6】:

          这是我一直在使用的一个解决方案,它使用 ReactDOM.render 但不依赖于函数的(已弃用)返回值。它使用回调(ReactDOM.render 的第三个参数)。

          如果不在浏览器中测试,请设置 jsdom:

          var jsdom = require('jsdom').jsdom;
          var document = jsdom('<!doctype html><html><body><div id="test-div"></div></body></html>');
          global.document = document;
          global.window = doc.defaultView;
          

          使用带有异步回调的 react-dom 渲染进行测试:

          var node, component;
          beforeEach(function(done){
              node = document.getElementById('test-div')
              ReactDOM.render(<MyComponent value={true} />, node, function() {
                  component = this;
                  done();
              });
          });
          
          it('should update the state of the component when the value prop is changed', function(done){
              // `component` will be updated instead of remounted
              ReactDOM.render(<MyComponent value={false} />, node, function() {
                  component = this;
                  // Assert that `component` has updated its state in response to a prop change
                  expect(component.state.value).toBe(false);
                  done();
              });
          });
          

          【讨论】:

            【解决方案7】:

            您可以使用酶来安装组件并为其添加道具:

            import React form 'react';
            import component;
            import {configure, mount} form 'enzyme';
            import Adapter from 'enzyme-adapter-react-16';
            import {expect} from 'chai';
            
            configure({adapter: new Adapter()});
            
            describe('Testing component', () => {
              let wrapper;
              beforeEach(() => {
                component = mount(<MyComponent value={false} />);
              });
              it('should update the state of the component when the value prop is changed', function(){
            
                expect(component.props().children.props.value).toBe(false);
            });
            

            【讨论】:

              【解决方案8】:

              TestUtils.renderIntoDocumentReactDOM.render 都使用来自 ReactDOM.render 的返回值。根据React docs

              ReactDOM.render() 当前返回对根 ReactComponent 实例的引用。然而,使用这个返回值是遗留的,应该避免,因为未来版本的 React 在某些情况下可能会异步渲染组件。如果您需要对根 ReactComponent 实例的引用,首选的解决方案是将回调 ref 附加到根元素

              如果我们接受这个建议并做这样的事情会怎样:

              let component, node;
              
              const renderComponent = (props = {}) => {
                ReactDOM.render(<MyComponent ref={r => component = r} {...props} />, node);
              }
              
              beforeEach(function(){
                  node = document.createElement('div');
                  renderComponent({value: true}, node); 
              });
              
              it('should update the state of the component when the value prop is changed', function(){
                  // `component` will be updated instead of remounted
                  renderComponent({value: false}, node); 
                  // Assert that `component` has updated its state in response to a prop change
                  expect(component.state.value).toBe(false);
              });
              

              【讨论】:

                【解决方案9】:

                最后一种方法(ReactDOM.render(&lt;MyComponent ref=...)不适用于 HOC 组件。

                【讨论】:

                  猜你喜欢
                  • 2021-12-15
                  • 1970-01-01
                  • 2019-04-07
                  • 2023-03-28
                  • 1970-01-01
                  • 2017-11-30
                  • 2019-05-23
                  • 2016-12-24
                  • 1970-01-01
                  相关资源
                  最近更新 更多