如何在 React 组件上测试 prop 更新
How to test a prop update on React component
对 React 组件道具更新进行单元测试的正确方法是什么。
这是我的测试夹具;
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.'
我只想测试 属性 的更新,而不是创建具有不同 属性 的元素的新实例。有没有更好的方法来执行此 属性 更新?
如果您在同一个容器节点中重新渲染具有不同道具的元素,它将被更新而不是重新挂载。参见 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);
});
警告:这实际上不会改变道具。
但对我来说,我只想在 componentWillReceiveProps
中测试我的逻辑。所以我直接打电话给myComponent.componentWillReceiveProps(/*new props*/)
。
我没有 need/want 测试 React 在 props 改变时调用方法,或者 React 在 props 改变时设置 props,只是当 props 与传入的不同时触发一些动画。
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);
});
这是一个较旧的问题,但如果其他人无意中发现了这个问题,以下设置对我来说效果很好:
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 运行 成为通常的组件更新。
TestUtils.renderIntoDocument
和 ReactDOM.render
都使用 ReactDOM.render
的返回值。根据 React docs:
ReactDOM.render() currently returns a reference to the root ReactComponent instance. However, using this return value is legacy and should be avoided because future versions of React may render components asynchronously in some cases. If you need a reference to the root ReactComponent instance, the preferred solution is to attach a callback ref to the root element
如果我们接受这个建议并做这样的事情会怎样:
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);
});
这是我一直在使用的解决方案,它使用 ReactDOM.render 但不依赖于函数中的(已弃用)return 值。它使用回调(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();
});
});
您可以使用酶来挂载组件并为其添加道具:
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);
});
快速添加,因为我正在寻找 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 来完成同样的事情。
最后一种方法(ReactDOM.render(<MyComponent ref=...
)不适用于 HOC 组件。
对 React 组件道具更新进行单元测试的正确方法是什么。
这是我的测试夹具;
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.'
我只想测试 属性 的更新,而不是创建具有不同 属性 的元素的新实例。有没有更好的方法来执行此 属性 更新?
如果您在同一个容器节点中重新渲染具有不同道具的元素,它将被更新而不是重新挂载。参见 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);
});
警告:这实际上不会改变道具。
但对我来说,我只想在 componentWillReceiveProps
中测试我的逻辑。所以我直接打电话给myComponent.componentWillReceiveProps(/*new props*/)
。
我没有 need/want 测试 React 在 props 改变时调用方法,或者 React 在 props 改变时设置 props,只是当 props 与传入的不同时触发一些动画。
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);
});
这是一个较旧的问题,但如果其他人无意中发现了这个问题,以下设置对我来说效果很好:
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 运行 成为通常的组件更新。
TestUtils.renderIntoDocument
和 ReactDOM.render
都使用 ReactDOM.render
的返回值。根据 React docs:
ReactDOM.render() currently returns a reference to the root ReactComponent instance. However, using this return value is legacy and should be avoided because future versions of React may render components asynchronously in some cases. If you need a reference to the root ReactComponent instance, the preferred solution is to attach a callback ref to the root element
如果我们接受这个建议并做这样的事情会怎样:
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);
});
这是我一直在使用的解决方案,它使用 ReactDOM.render 但不依赖于函数中的(已弃用)return 值。它使用回调(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();
});
});
您可以使用酶来挂载组件并为其添加道具:
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);
});
快速添加,因为我正在寻找 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 来完成同样的事情。
最后一种方法(ReactDOM.render(<MyComponent ref=...
)不适用于 HOC 组件。