How to test a prop update on React component

59,745

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();
    });
});
Share:
59,745

Related videos on Youtube

Win
Author by

Win

Updated on July 09, 2022

Comments

  • Win
    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
    arkoak about 9 years
    In 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
    Michelle Tilley about 9 years
    If you're testing a response to props changes in something like componentWillReceiveProps or componentDidUpdate, 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
    Jon Rubins over 8 years
    +1 for the answer. Note that this is now ReactDOM.render instead of just React.render (as of version 0.14 I think).
  • Yaniv Efraim
    Yaniv Efraim over 7 years
    Both TestUtils.renderIntoDocument and ReactDOM.render uses the returned value from ReactDOM.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
    prime over 6 years
    what if MyComponent is wrapped around a Provider ?
  • pmiranda
    pmiranda almost 4 years
    But the OP didn't mentioned Enzyme, neither in their tags
  • Harsh Phoujdar
    Harsh Phoujdar about 3 years
    What if we have mounted the component ?
  • user1095118
    user1095118 about 3 years
    I 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.