问题
What is the correct way of unit testing a React component prop update.
Here is my test fixture;
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);
});
});
This works fine and the test passes, however this displays a react warning message
'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.'
All i want to test is the update of a property, not to create a new instance of the element with a different property. Is there a better way to do this property update?
回答1:
If you re-render the element with different props in the same container node, it will be updated instead of re-mounted. See React.render.
In your case, you should use ReactDOM.render
directly instead of TestUtils.renderIntoDocument
. The later creates a new container node every time it is called, and thus a new component too.
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);
});
回答2:
AirBnB's Enzyme library provides an elegant solution to this question.
it provides a setProps method, that can be called on either a shallow or jsdom wrapper.
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);
});
回答3:
Caveat: this won't actually change props.
But for me, all I wanted was to test my logic in componentWillReceiveProps
. So I'm calling myComponent.componentWillReceiveProps(/*new props*/)
directly.
I didn't need/want to test that React calls the method when props change, or that React sets props when props change, just that some animation is triggered if the props differ to what was passed in.
回答4:
Here's a solution I've been using that uses ReactDOM.render but doesn't rely on the (deprecated) return value from the function. It uses the callback (3rd argument to ReactDOM.render) instead.
Setup jsdom if not testing in the browser:
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;
Test using react-dom render with async callback:
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();
});
});
回答5:
This is an older question, but in case anyone else stumbles upon this, the following setup has worked for me nicely:
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
});
This makes React run the usual component update.
回答6:
Both TestUtils.renderIntoDocument
and ReactDOM.render
uses the returned value from ReactDOM.render
. According to 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
What if we take this advise and do something like this:
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);
});
回答7:
You can use enzyme to mount components and add props to it:
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);
});
来源:https://stackoverflow.com/questions/30614454/how-to-test-a-prop-update-on-react-component