How to test a prop update on React component
Solution 1
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);
});
Solution 2
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);
});
Solution 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.
Solution 4
Quick addition, as I was looking for an answer for testing-library, and didn't find it here: there's an example in this issue, and it looks like this:
const {container} = render(<Foo bar={true} />)
// update the props, re-render to the same container
render(<Foo bar={false} />, {container})
Alternatively, testing-library now offers a rerender
method that accomplishes the same thing.
Solution 5
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();
});
});
Related videos on Youtube
Win
Updated on July 09, 2022Comments
-
Win almost 2 years
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?
-
arkoak about 9 yearsIn actual practice, the render is not called explicitly in all cases. A better way is to use state data change to trigger the re-render as there may be some other hooks in live environment which you are missing out on by directly calling state change.
-
Michelle Tilley about 9 yearsIf you're testing a response to props changes in something like
componentWillReceiveProps
orcomponentDidUpdate
, etc., this is how I would do it as well. That said, I would try to rewrite the component so that the value in state is calculated dynamically at render-time instead, if possible (instead of using state). -
Jon Rubins over 8 years+1 for the answer. Note that this is now
ReactDOM.render
instead of justReact.render
(as of version 0.14 I think). -
Yaniv Efraim over 7 yearsBoth
TestUtils.renderIntoDocument
andReactDOM.render
uses the returned value fromReactDOM.render
. According to React docs: "using this return value is legacy and should be avoided because future versions of React may render components asynchronously in some cases". Is there a better solution which avoids the use of ReactDOM.render output? -
prime over 6 yearswhat if
MyComponent
is wrapped around aProvider
? -
pmiranda almost 4 yearsBut the OP didn't mentioned Enzyme, neither in their tags
-
Harsh Phoujdar about 3 yearsWhat if we have mounted the component ?
-
user1095118 about 3 yearsI would not recommend testing using this approach in '21 although I don't know specifically what you are testing I would be focused on asserting that the application of the prop change is correctly rendered in the component output rather than the value of the prop itself.