如何测试 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。更好的方法是使用状态数据更改来触发重新渲染,因为在实时环境中可能存在一些其他钩子,而您通过直接调用状态更改可能会错过这些钩子。 如果您正在测试对componentWillReceiveProps
或 componentDidUpdate
等属性更改的响应,我也会这样做。也就是说,如果可能的话,我会尝试重写组件,以便在渲染时动态计算 state 中的值(而不是使用 state)。
+1 为答案。请注意,现在是 ReactDOM.render
而不仅仅是 React.render
(我认为是 0.14 版)。
TestUtils.renderIntoDocument
和 ReactDOM.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.renderIntoDocument
和 ReactDOM.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(<MyComponent ref=...
)不适用于 HOC 组件。
【讨论】:
以上是关于如何测试 React 组件的 prop 更新的主要内容,如果未能解决你的问题,请参考以下文章