soapergem
soapergem

Reputation: 10029

How can I trigger a state change in a unit test with React DOM?

I'm using the React Test Utilities to unit test some of my code. I call renderIntoDocument to render a custom component and then use findDOMNode to test out what got rendered. The trouble I'm running into is that I'm not sure how to update the state and effectively trigger a re-render within the context of a unit test.

Here's some sample code -- pay attention to the code comment:

import React from 'react';
import ReactDOM from 'react-dom';
import TestUtils from 'react-dom/test-utils';
import MyCustomComponent from '../../app/components/MyCustomComponent';

describe('My Test Suite', () => {
    let component, node;
    test('verify state change', () => {
        const items = [{'value': '1'}];
        component = TestUtils.renderIntoDocument(
            <MyCustomComponent items={items} />
        );
        node = ReactDOM.findDOMNode(component);
        expect(node.querySelector('input[type=text]').value).toEqual('1');
        component.state.items = [{'value': '2'}];
        // do something here to trigger a re-render?
        expect(node.querySelector('input[type=text]').value).toEqual('2');
    });
});

Unfortunately it seems simply changing the state variable doesn't do anything. And I can't call component.componentWillReceiveProps() because that doesn't seem to be defined.

Please note that I do want the same component to call its render function rather than replacing it with, effectively, a brand new component. The reason is because I found a bug where the component was rendering things based on this.props instead of this.state, and I want a test to show that it's always using data from the state and not from the initial values.

Upvotes: 1

Views: 7121

Answers (1)

Parker Ziegler
Parker Ziegler

Reputation: 1020

Enzyme from AirBnb has some great utilities for this. You'll need to install the dependencies but it's simple enough to get it configured. Then, you can simply call Enzyme's setState method on your component instance. An important note – your "component instance" in this case is a shallow rendered component. Your code would look something like this:

import React from 'react';
import MyCustomComponent from '../../app/components/MyCustomComponent';
import { shallow, configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

// configure your adapter
configure({ adapter: new Adapter() });

describe('My Test Suite', () => {

    test('verify state change', () => {
        const items = [{'value': '1'}];
        const wrapper = shallow(<MyCustomComponent items={items} />);

        // find your shallow rendered component and check its value
        expect(wrapper.find('input[type="text"]').value).toEqual('1');

        // set the state of the component
        wrapper.setState({ items: [{'value': '2'}] });

        // state should be updated, make sure its value was properly set
        expect(wrapper.find('input[type="text"]').value).toEqual('2');
    });
});

All of this assumes that you are using state in your component properly. In your case, items appears to be passed in as a prop. If you are setting state by just copying props, you may want to rethink your strategy. In any case, this approach should be identical to how state updates in React work – you're operating on the same component instance without unmounting and remounting the component. Hope this helps.

Upvotes: 7

Related Questions