如何测试 React 组件的 prop 更新

Posted

技术标签:

【中文标题】如何测试 React 组件的 prop 更新【英文标题】:How to test a prop update on React component 【发布时间】: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.'

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

【问题讨论】:

【参考方案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 组件。

【讨论】:

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

[react] 在React中组件的props改变时更新组件的有哪些方法?

我可以在 React.js 中更新组件的 props 吗?

react 子组件获取父组件传来的 props 问题

react父组件传入的属性没有让子组件收到的prop变化

[react] 怎么定时更新一个组件?

在 React Form 中更新 props 更改的状态