6th October 2015

Fundamental to React is the concept of props. Components can contain other components and pass down properties to them in the form of props. These components can then behave differently depending on the values of these props which makes a good case for writing unit tests to make sure that your components are handling changes in the values of these props correctly.

However, testing in isolation means that a child component in your app will be tested on it’s own and not within it’s usual parent so any changes to props will have to be managed manually within your tests. This article will show you how props can be tested for components created with both React.createClass() and React.Component.

Note: all example code in this article is written using ES6 and JSX with tests using the React TestUtils and Jasmine BDD testing framework. The latest released version of React at time of writing was 0.13.3

React.createClass()

Prior to version 0.13, components were created in React using the React.createClass() method. Testing changes to props for components created in this way is relatively straightforward as the setProps() method is available and can be passed an object to update the props of the component (much like how setState() works for updating state) which will then trigger a re-render.

As an example, here is a test for a simple component which just outputs the value of the text prop in a div:

import React from 'react';

const TestComponent = React.createClass({
  render() {
    return <div>{this.props.text}</div>;
  }
});

export default TestComponent;

and this is a test to make sure that the text in the div is updated when the props are changed:

import React from 'react/addons';
const TestUtils = React.addons.TestUtils;

import TestComponent from 'path/to/TestComponent';

describe('Testing props change', () => {

  it('should update the div text when the text prop is changed', () => {
  
    let instance, div, divDOM;
  
    // render an instance of the TestComponent
    instance = TestUtils.renderIntoDocument(<TestComponent text="original text" />);
    
    // find the rendered div
    div = TestUtils.findRenderedDOMComponentWithTag(instance, 'div');
    divDOM = React.findDOMNode(div);
    
    // the div text should be the same as the text prop
    expect(divDOM.textContent).toBe('original text');
    
    // update the props
    // setProps is asynchronous so any tests after calling it should be within a callback
    // to be certain the changes have been processed
    instance.setProps({
      text: 'new text'
    }, () => {      
      
      // find the div again
      div = TestUtils.findRenderedDOMComponentWithTag(instance, 'div');
      divDOM = React.findDOMNode(div);
      
      // make sure the text has changed as expected
      expect(divDOM.textContent).toBe('new text');
      
    });
  
  });

});

React.Component

React 0.13 introduced support for ES6 classes. The added convenience and power of being able to use native classes came at the price of losing several existing methods from React.createClass() including setProps(). This means that another approach is required for testing prop changes on these component types.

This solution to this is to create another component to act as a container for the component that is to be tested. The state of that container can be passed down to the test component as it’s props and whenever changing props needs to be tested, we can just update the state of the container which will trigger it to re-render and pass down it’s updated state as the new props of the test component.

This example demonstrates how this solution can be implemented for a simple ES6 class component created by extending React.Component:

import React from 'react';

class TestComponent extends React.Component {
  render() {
    return <div>{this.props.text}</div>;
  }
}

export default TestComponent;

Setting up the container in multiple files will get tedious so this reusable helper function can be utilised used to render the container and the component inside of it.

The helper function takes the component to test and it’s default props as arguments. It then internally creates a container class that will set the default props as it’s state, and render the test component inside of it, passing down the state as props.

The container is then rendered to the document and finally the function returns both the container and test component so that the tests can have access to them both.

import React from 'react/addons';
const TestUtils = React.addons.TestUtils;

/**
 * Renders a component inside of a container
 * 
 * @param {React.Component} component The component to test
 * @param {Object} componentProps={}  The default props to set on the component
 * @return {Array} The container component as the first item, the component to test as the second item
 */
function renderInContainer(component, componentProps={}) {

  class PropChangeContainer extends React.Component {
  
    constructor(props) {
    
      super(props);
      
      // set the state of the container from it's props (which will be the default
      // componentProps) passed to the function
      this.state = props;
      
    }
    
    render() {
    
      // render the component within the container and pass the container state
      // as the component's initial props
      return React.createElement(component, this.state);
      
    }
    
  }

  // get both the container and component instances and return them
  let container = TestUtils.renderIntoDocument(<PropChangeContainer {...componentProps} />);
  let instance = TestUtils.findRenderedComponentWithType(container, component);

  return [
    container,
    instance
  ];

}

export default renderInContainer;

Finally, the test file uses the above renderInContainer() function and makes changes the state of the container component which in turn updates the props on the test component and allows changes to it’s props to be tested.

import React from 'react/addons';
const TestUtils = React.addons.TestUtils;

import renderInContainer from 'testutils/renderInContainer';
import TestComponent from 'path/to/TestComponent';

describe('Testing props change', () => {

  it('should update the div text when the text prop is changed', () => {
  
    let container, instance, div, divDOM;
  
    // render an instance of the TestComponent inside a container and set the default props
    [container, instance] = renderInContainer(TestComponent, { text: 'original text' });
    
    // find the rendered div inside instance
    div = TestUtils.findRenderedDOMComponentWithTag(instance, 'div');
    divDOM = React.findDOMNode(div);
    
    // the div text should be the same as the text prop
    expect(divDOM.textContent).toBe('original text');
    
    // update the state of the container
    // this will trigger a re-render on the container which will then pass down 
    // the updated state to the instance as it's new props
    container.setState({
      text: 'new text'
    }, () => {      
      
      // find the div again
      div = TestUtils.findRenderedDOMComponentWithTag(instance, 'div');
      divDOM = React.findDOMNode(div);
      
      // make sure the text has changed as expected
      expect(divDOM.textContent).toBe('new text');
      
    });
  
  });

});

Conclusion

These are just two approaches to testing changes to props in React. Do you use any other methods for this? Let me know in the comments!

And if none of this made any sense then be sure to check out the React TestUtils docs and the React Testing Tutorial on the Jest website for more information on getting started with testing in React.



Join in the Discussion