What is the correct way of unit testing a React component prop update.
Here is my test fixture;
describe(\'updating the value\', function(){
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);
});
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);
});
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);
});
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.
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();
});
});
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.