Dynamic user interfaces began to be an important feature for today web applications. To maintain quality, they must be tested properly. There are variety of approaches - each has its pros and cons. As a React developer you may need to choose a proper approach to test your components in an effective way.

Two axes of testing components

When you test React components, there are at least two concerns that must be tested:

  • Given properties and state, what structure our rendered tree will have?
  • Given an output of render, is there a possibility to transition from state A to state B?

Those two concerns have their own approaches to test them. I usually name the first concern as testing structure, and the second one as testing behaviour. These concerns are separate, but some of testing structure approaches can affect available ways of testing behaviour approaches.

Testing structure: the real DOM

Since React components render a real DOM based on the virtual one, one approach is to get an actual DOM and test against it. Testing with this approach works well when you want to test things in a more ‘acceptance’ way. Components rendered can affect the outcome of such test.

To illustrate how to test with this approach, I’ll use this simple component - the Greeter:


import React from 'react/addons';

class Greeter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { name: this.props.initialName };

    this.greeterText = this.greeterText.bind(this);
  }

  greeterText() {
    return (<p>Hello, {this.state.name}!</p>);
  }

  render() {
    return (<div className="greeter">
              {this.greeterText()}
            </div>);
  }
}

Greeter.defaultProps = { initialName: "World" };

export default Greeter;

Asserting the structure of a component like this can be done like this:


import Greeter from 'components/Greeter';
import React   from 'react/addons';

describe("Greeter component - the static output approach", () => {
  beforeEach(function() {
    let {TestUtils} = React.addons;

    this.component = TestUtils.renderIntoDocument(<Greeter initialName="my first test" />);
    this.renderedDOM = () => React.findDOMNode(this.component);
  });

  it("renders a paragraph which greets someone", function() {
    let renderedParagraphs = this.renderedDOM().querySelectorAll("p");

    expect(this.renderedDOM().children.length).toEqual(1);
    expect(renderedParagraphs.length).toEqual(1);
    expect(renderedParagraphs[0].textContent).toEqual("Hello, my first test!");
  });

  it("wraps a paragraph with a <div> with a proper class name", function() {
    let rootElement = this.renderedDOM();

    expect(rootElement.tagName).toEqual("DIV");
    expect(rootElement.classList.length).toEqual(1);
    expect(rootElement.classList[0]).toEqual("greeter");
  });
});

TestUtils.renderIntoDocument is used to render a component in a real, but detached element of the DOM. You will see this pattern everytime when you test React component with the real rendering to DOM. This allows you to omit the rather inconvenient creating <div>‘s using document.createElement by yourself.

As you can see, there is React.findDOMNode used to get the real DOM of rendered component. Then I’m using methods from the Document Object Model Web API to assert the structure of the DOM tree. With this approach you can use many convenient tools to make such assertions easier. One of the most popular libraries for querying the DOM tree is jQuery which can be used there.

This approach is less useful for testing components which compose their output based on mostly user-defined components - you lose a lot of info when using this technique. It is perfect for lowest level components which uses only DOM components to render itself. It is also a very good fit if your intent is to test something in an acceptance way - you can’t be closer to user’s experience if you use this approach and testing behaviour with Simulate.

Pros:

  • Your test can survive even switches of tools with small modifications. Everything that renders a real DOM can be tested that way - not only React. Assertions will stay the same.
  • This is the most 'direct’ way to test an outcome. You don’t even need to trust React that it makes its job to render the DOM.
  • There are variety of tools that can help you with querying the DOM like jQuery.

Cons:

  • You must have an environment which supports writing to DOM. It can be a virtual one (jsdom) or a ‘real’ one (like PhantomJS or a real browser driver). It can slow down your tests.
  • Testing ‘reactish’ things like props is harder with this approach if they’re not placed at the component you are testing.
  • You have to test in terms of the DOM elements. You can’t test a created component tree this way.
  • It is a bit verbose. While your component is defined declaratively, here you have to query the DOM result in an imperative way.
  • Testing behaviour through setting state and calling properties can be hard with this approach.

Testing structure: Test Utilities

The first approach was all about testing the rendered real DOM. Since React maintains the virtual DOM there is a way to test your component in terms of it too. This way you can test the component tree structure. This is a less direct way to test the output. It has a big advantage though - you can test whether a proper component is created during render and leave the details about the real DOM structure of such component to the lower-level test.

To test your components this way you need to use methods from TestUtils module which comes with React addons. Namely, these methods are used:

  • findAllInRenderedTree - to perform an arbitrary search in rendered component tree. This is the most powerful method in the set. It traverses through the whole component tree and returns all components that returns truthy value in a predicate you provided.
  • scryRenderedDOMComponentsWithClass - to get DOM components (those representing DOM elements) by a CSS class. It returns a list of such components.
  • findRenderedDOMComponentWithClass - the same as above, but throws an error when there is more than one component with a given CSS class. It returns a component itself, not a list.
  • findRenderedDOMComponentWithTag - the same as above, but it matches tags (like "li" or "div").
  • scryRenderedComponentsWithType - returns all components (no matter DOM components or user-defined ones) which matches given type (which can be a component class function or a name of DOM component like "li").
  • findRenderedComponentWithType - the same as above but throws when there is more than one component of a given type.
  • isDOMComponent - generally useful when checking if a top-level rendered component or a result of findAllInRenderedTree is a DOM component of a given type.

An example to this technique would be a simple invitation list component, which is storing people invited to an event. The code for this InvitationList component looks like this:


class InvitationList extends React.Component {
  constructor(props) {
    super(props);
    this.state = { invitedPeople: this.props.initiallyInvited };
  }

  personUniqueID(person) {
    return ['person', person.id].join('_');
  }

  render() {
    let invitedPeople = [];
    this.state.invitedPeople.forEach(person => {
      let id = this.personUniqueID(person);
      invitedPeople.push(<InvitedPerson ref={id} key={id} person={person} />);
    });

    return (
      <div className='invitation-list-container'>
        <ul ref='list' className='invitation-list'>
          {invitedPeople}
        </ul>
      </div>
    );
  }
}

Here is the test made with Test Utilities approach:


import React from 'react/addons';
import { InvitedPerson, InvitationList } from 'components/InvitationList';

describe("Invitation List - testing with test utilities", () => {
  beforeEach(function() {
    this.examplePeople = [
      { id: 1, name: "Waldo" },
      { id: 2, name: "Hercules" }
    ];

    let { TestUtils } = React.addons;

    this.TestUtils = TestUtils;
    this.component = TestUtils.renderIntoDocument(<InvitationList initiallyInvited={this.examplePeople} />);
  });

  it("renders a list in a box with proper CSS classes", function() {
    let box = this.TestUtils.findRenderedDOMComponentWithTag(this.component, "div");
    expect(box.props.className).toEqual("invitation-list-container");

    let list = this.TestUtils.findRenderedDOMComponentWithTag(box, "ul");
    expect(list.props.className).toEqual("invitation-list");
  });

  it("shows a list of people who are invited to an event", function() {
    let list = this.TestUtils.findRenderedDOMComponentWithTag(this.component, "ul");

    expect(list.props.children.length).toEqual(2);

    list.props.children.forEach((invitedPerson, index) => {
      expect(invitedPerson.type).toEqual(InvitedPerson);
      expect(invitedPerson.key).toEqual(`person_${this.examplePeople[index].id}`);
      expect(invitedPerson.ref).toEqual(`person_${this.examplePeople[index].id}`);
      expect(invitedPerson.props.person).toEqual(this.examplePeople[index])
    });
  });
});

As you may see, preparation of such test is the same as with the real DOM testing. TestUtils.renderIntoDocument is called to get a component properly rendered in a detached DOM node. Then an API is used to assert certain properties about the virtual DOM. What is important here is that an implementation of "shows a list of people who are invited to an event" test case cannot be expressed in the real DOM approach. All details about how InvitedPerson is rendered can be thrown to the test of the InvitedPerson itself.

This approach is very good both for testing ‘higher-level’ components (these that uses other user-defined components to achieve their goals) and ‘lowest level’ ones (which are ‘leafs’ in your user interface - they render only a DOM markup). It is made for more ‘unit-like’ tests - when testing in an acceptance way the real DOM approach can be more reliable.

Pros:

  • It follows the render structure more closely since you can test against user-defined components too.
  • You can test ‘reactish’ properties of the rendered output - like keys, refs, properties and state of the lower-level components.
  • It is easier to use some testing behaviour approaches - and all are possible with this approach. For example, you can test by directly setting state or calling properties in an easy way.

Cons:

  • It shares the same ‘verbosity’ of testing like the real DOM testing approach. Test Utilities methods encourages to query the virtual DOM in an imperative way.
  • It shares the environment requirements with real DOM testing approach. It may affect the performance of your test suite.
  • If you want to test a component in a total isolation - without side-effects of the lower-level components it is not possible with this approach.
  • You cannot use tools like jQuery to ease your pain of imperatively querying the component’s tree, since you are operating on a virtual DOM.

Testing structure: Shallow rendering

Shallow rendering approach is the newest API for testing structure provided by React.js itself. It has really nice properties - you can test your components in a more declarative way than with previous approaches. It is perfect for testing in an isolation - lower-level components cannot affect your test since they are not rendered at all.

An idea behind this approach is simple. While in previous approach you make a real rendering of the component, here you provide a special renderer which renders only a top-level component within your render. The rest of the tree is not rendered at all - they stay as React elements. You have an access to properties of such elements, so you can still affect the state, making your component re-rendering itself. That means you can test behaviour too with this approach.

Tools used in this approach are still under development so they lack some features - like accessing the component itself and using references. This may change in the future, but for now it is a little harder (or impossible) to test everything that you may test with other approaches in an easy way.

To display the power of this technique the same example of InvitationList will be used. Here is the test:


import React from 'react/addons';
import { InvitedPerson, InvitationList } from 'components/InvitationList';

describe("Invitation List - testing with shallow rendering", () => {
  beforeEach(function() {
    this.examplePeople = [
      { id: 1, name: "Waldo" },
      { id: 2, name: "Hercules" }
    ];

    let { TestUtils } = React.addons;

    this.TestUtils = TestUtils;
    this.renderer = TestUtils.createRenderer();
    this.renderer.render(<InvitationList initiallyInvited={this.examplePeople} />);
  });

  it("renders a list in a box with proper CSS classes and people within it", function() {
    let result = this.renderer.getRenderOutput(),
        personOneID = `person_${this.examplePeople[0].id}`,
        personTwoID = `person_${this.examplePeople[1].id}`;

    expect(result.type).toEqual('div');
    expect(result.props.className).toEqual("invitation-list-container");

    expect(result.props.children).toEqual(
      <ul className="invitation-list" ref='list'>
        <InvitedPerson key={personOneID} ref={personOneID} person={this.examplePeople[0]} />
        <InvitedPerson key={personTwoID} ref={personTwoID} person={this.examplePeople[1]} />
      </ul>
    );
  });
});

With this approach the flow is a little different than with other approaches. First of all, you do not render your component using renderIntoDocument. In this approach you create a special renderer itself, using TestUtils.createRenderer() method. If you have the rendered defined, you use the render() method of it to render your component in a shallow way. Then you can get the output using getRenderOutput() method.

When a component is re-rendered, you have to call getRenderOutput() again. This is different than testing with other approaches where you have ‘live’ references to virtual or real DOM and you do not need to do so manually.

As you may see this test is much more concise than an approach with using TestUtils API. That is because you can define assertions in terms of React elements - so you are using the same syntax as you use in render. It makes such test easier to read and much less convoluted. What’s worth to notice is that you have very easy access to properties and state of the ‘root’ element rendered. Since lower-level components (other than the top component rendered) are not instantiated at all, they cannot affect your test - and you cannot get to their internals (like output they render) too. Because of this they cannot affect the outcome of your test at all.

This approach is perfect when you want to test your component in isolation. Unfortunately, since you have no access to the component itself it’s hard to test behaviour the other way than to traverse the tree of elements itself and calling methods from properties. Also, with this approach you cannot test your components in an acceptance way.

Pros:

  • Tests written this way are usually more concise than tests written based on other approaches.
  • Lower-level components cannot affect your test outcome at all (except the top one), making it a great tool for unit testing.
  • You do not need a real DOM to test your components - every JavaScript environment could do. That makes tests written this way very fast.
  • You can test all reactish properties very easily - just assert it using JSX like on the example above.
  • It is the preferred way of testing React components suggested by React development team. That means they’ll surely grow this API to be even more useful.

Cons:

  • Since lower-level components are not instantiated, you cannot assert their state.
  • You cannot test your components in an acceptance way using this approach.
  • There is no way to get your component instance back (oficially, there are hacks for that). This API is still developing and there is a great chance that it’ll be added.
  • References does not work with this approach. They may be added in future, but for now you cannot use them.
  • There are no tools to traverse React element trees, meaning that you may end in an ugly chaining of props.children.props.children… if you want to call a method somewhere deep in the component tree. But you may write or use your own tools.

Testing structure: through references

This approach is just an enhancement for both testing through Test Utils and the real DOM approach. By using React references you can decrease the quantity of imperative querying - thus making your tests a little bit more concise. Since references do not work with shallow rendering (yet) you can’t enhance your workflow with them with this approach. It has an ugly side-effect of adding references for testing - thus making changes in tested code for the sake of making it testable. If you can live with that this can be a great way to to make your tests a little more concise.

You can also use references to overcome the problem of real DOM approach not knowing about the shadow DOM structure. You can test the component with real DOM to an extent, and then just check if you ‘hit’ the boundary of a reference and then stop testing.

To show how you can test with references the old InvitationList example will be used. Just go back to the code of this component and be aware of references made within it (it is a ref property in JSX within the render method).

The test with references looks like this:


import React from 'react/addons';
import { InvitationList, InvitedPerson } from 'components/InvitationList';

describe("Invitation List - testing through refs", () => {
  beforeEach(function() {
    let { TestUtils } = React.addons;

    this.examplePeople = [
      { id: 1, name: "Waldo" },
      { id: 2, name: "Hercules" }
    ];

    this.component = TestUtils.renderIntoDocument(<InvitationList initiallyInvited={this.examplePeople} />);
    this.TestUtils = TestUtils;
  });

  it("renders a list in a box with proper CSS classes", function() {
    let renderedDOM = React.findDOMNode(this.component);
    let listDOM = React.findDOMNode(this.component.refs.list);

    expect(renderedDOM.tagName).toEqual("DIV");
    expect(renderedDOM.classList.length).toEqual(1);
    expect([].slice.call(renderedDOM.classList)).toContain("invitation-list-container");

    expect(renderedDOM.children.length).toEqual(1);
    expect([].slice.call(renderedDOM.children)).toContain(listDOM);

    expect(listDOM.tagName).toEqual("UL");
    expect(listDOM.classList.length).toEqual(1);
    expect([].slice.call(listDOM.classList)).toContain('invitation-list');
  });

  it("shows a list of people who are invited to an event", function() {
    let firstPersonComponent  = this.component.refs[this.component.personUniqueID(this.examplePeople[0])];
    let secondPersonComponent = this.component.refs[this.component.personUniqueID(this.examplePeople[1])];

    let assertProperPresentationOfPerson = (component, person) => {
      expect(component).toBeDefined();
      expect(component.props.person).toEqual(person);
      expect(this.TestUtils.isCompositeComponentWithType(component, InvitedPerson)).toBeTruthy();
    };

    assertProperPresentationOfPerson(firstPersonComponent, this.examplePeople[0]);
    assertProperPresentationOfPerson(secondPersonComponent, this.examplePeople[1]);
  });
});

As you may see, this component is tested with a real DOM approach. But when it comes to testing list elements, I’m not going deep with them. I take a reference and check the type and properties passed. A DOM structure of InvitedPerson can be then tested in a test of the InvitedPerson component.

This approach is great for testing components which have some kind of lists generated in a dynamic way - by keeping references you can check if the arbitrary list element gets the valid props and state without mundane traversing through a component’s tree. It shares the pros and cons of those approaches, but it makes your tests more concise - making a component’s code a little more ‘dirty’. You must decide by yourself whether you want to make this trade-off.

Testing behaviour: Simulating events

When it comes to testing behaviour, the simplest thing that could get in mind is to make it work just like the user. Clicking the buttons, changing input values just like an user can is a nice way to test whether your component behaves correctly - and it is the most usual concern you’d like to ask. It may be tempting to just issue such events by yourself, especially if you are testing structure with the real DOM approach. Unfortunately, you can’t do so since React is using synthetic events to ensure cross-browser compatibility between implementations of the event system. Fortunately, TestUtils provides a way to issue such events. There is a module called Simulate which takes care about it.

The API is defined in terms of what events React can handle - so when you want to check a behaviour attacked to the onClick event, Simulate.click is your friend. The same is true with another kind of events - hovering mouse, changing inputs, focusing them and so on.

To show the example of this approach, there is a rather simple component which renders a select list of dishes - SpecialityOfTheHouse. The code of this component looks like this:


import React from 'react/addons';

class SpecialityOfTheHouse extends React.Component {
  constructor(props) {
    super(props);

    this.state = { selectedSpeciality: 'fried-bacon' };
    this.changeSelectedSpeciality = this.changeSelectedSpeciality.bind(this);
  }

  changeSelectedSpeciality(event) {
    this.setState({ selectedSpeciality: event.target.value });
  }

  speciality(name, value) {
    return (<option key={value} value={value}>{name}</option>);
  }

  render() {
    return (<select value={this.state.selectedSpeciality}
                    onChange={this.changeSelectedSpeciality}>
              {this.speciality("Fried Bacon", "fried-bacon")}
              {this.speciality("Fish & Chips", "fish-and-chips")}
              {this.speciality("Potato Salad", "potato-salad")}
            </select>);
  }
}

export default SpecialityOfTheHouse;

As you may see, it is a plain old <select> with predefined values. To test it, a Simulate module will be used.

The testing of this component’s behaviour is expressed in this test. To test structure, a real DOM approach is used.


 import React from 'react/addons';
import SpecialityOfTheHouse from 'components/SpecialityOfTheHouse';

describe("Speciality of the House - using testing through Simulate technique", () => {
  beforeEach(function() {
    let { TestUtils, TestUtils: { Simulate } } = React.addons;

    this.component = TestUtils.renderIntoDocument(<SpecialityOfTheHouse />);
    this.selectDOM = () => React.findDOMNode(this.component);
    this.dishesDOM = () => this.selectDOM().children;

    this.selectedDishDOM = () => {
      let selectedDishDOM = undefined;
      let dishesDOM = this.dishesDOM();
      for(let index = 0; index < dishesDOM.length; ++index) {
        if(dishesDOM[index].selected) {
          selectedDishDOM = dishesDOM[index];
          break;
        }
      }

      return selectedDishDOM;
    };

    this.availableDishes = () => {
      let dishes = [];

      // [].slice.call converts everything iterable into a *real* array.
      // Only real Arrays got forEach method!
      [].slice.call(this.dishesDOM()).forEach(function(dishDOM) {
        let { textContent, value } = dishDOM;
        dishes.push({ value, name: textContent });
      });

      return dishes;
    };

    this.Simulate = Simulate;
  });

  it("renders a select box", function() {
    expect(this.selectDOM().tagName).toEqual("SELECT");
  });

  it("has fried bacon selected by default", function() {
    let selected = this.selectedDishDOM();

    expect(selected).toBeDefined();
    expect(selected.textContent).toEqual("Fried Bacon");
    expect(selected.value).toEqual('fried-bacon');
  });

  it("has all restraurant's best dishes", function() {
    let dishesPresented = this.availableDishes();

    expect(dishesPresented.length).toEqual(3);
    expect(dishesPresented).toContain({ name: "Fried Bacon", value: "fried-bacon" });
    expect(dishesPresented).toContain({ name: "Fish & Chips", value: "fish-and-chips" });
    expect(dishesPresented).toContain({ name: "Potato Salad", value: "potato-salad" });
  });

  it("allows changing speciality of the house by the user", function() {
    this.Simulate.change(this.selectDOM(), { target: (this.dishesDOM())[1] });

    expect(this.component.state.selectedSpeciality).toEqual("fish-and-chips");
    expect(this.selectedDishDOM().value).toEqual("fish-and-chips");

    this.Simulate.change(this.selectDOM(), { target: (this.dishesDOM())[2] });

    expect(this.component.state.selectedSpeciality).toEqual("potato-salad");
    expect(this.selectedDishDOM().value).toEqual("potato-salad");
  });
});

To test whether a dish is selected by default, a DOM querying is used. It could be done in another way - by testing the state directly. Approach presented is a more acceptance way to test it.

What is important in testing by Simulate is within a "allows changing speciality of the house by the user" test case. As you can see, it gets a DOM element (it can get a component - so it’s suitable for TestUtils approach too) and event details - and emits the event specified by a method from the Simulate module.

This type of testing behaviour is a must in acceptance way of testing. It works very well in all situations where it is possible to use it.

Pros:

  • You can’t get into a state that is not possible to achieve by the user - in this approach you just mock the user actions.
  • It can be used with both real DOM and TestUtils approach of testing structure.
  • Since you have an access to ‘internal’ properties of an event, this API is a very powerful tool to test user interactions with your components.
  • It aligns very well with an acceptance way of testing. It also tests whether you binded your event handlers properly.

Cons:

  • You need an environment with DOM to use this approach. Using it without DOM is a work in progress - so it is likely to be possible in newer versions of React.
  • When you are iterating on a component, you need to be sure you plugged event handlers before you start testing behaviour. This can be a limitation when you want to make the smallest steps possible.
  • You can only test user interactions this way. Testing other behaviour (like timers) is not possible with this approach.
  • It can be hard to use this technique with the shallow rendering approach to test structure.

Testing behaviour: Directly setting state

When iterating fast to create React components you have to be focused mostly on states you’d like to present - thus on a render method. Testing whether state is rendered properly, without thinking about binding handlers can be really helpful in a TDD flow. setState approach excels in this kind of workflow - but finally you may need to replace it with other approaches.

Since testing through setState can lead you to an arbitrary state - even impossible to achieve by user interactions and other ‘sources’ of state change, it demands a great care from the programmer. This is rather considered an anti-pattern - but I want you to show this technique since it is great while iterating in a TDD cycles.

To show this technique, consider testing Greeter. It is a component which is in a middle of work - I prepared a state to change, but it can’t be changed for now. I can add an input, or a button to change it later - but I can still test behaviour attached to this state by using setState in the test directly.

This is an example of such test case:

  it("changes greeted person after setting state", function() {
    let renderedParagraph = this.renderedDOM().querySelectorAll("p")[0];

    expect(renderedParagraph.textContent).toEqual("Hello, my first test!");
    this.component.setState({ name: "World" });
    expect(renderedParagraph.textContent).toEqual("Hello, World!");
  });

You should not test behaviour in a final test using this technique. It is intented to be used while iterating on a component. When you provide a handler or method to change it by an user interaction or another source of state change, you should switch to testing it using Simulate or by calling methods immediately.

Pros:

  • Allows you to test behaviour during iterating on a component.
  • Works with both TestUtils and real DOM approaches of testing structure.
  • Does not have any environment requirements at all.

Cons:

  • It is considered an anti-pattern. You should not leave tests with this technique used.
  • It allows to set an arbitrary state - even the impossible one. You may end testing cases which cannot be set by your code.

Testing behaviour: Calling properties and methods

There are state changes that are not triggered by an user interaction, but other actors. These can be timers, some kind of real-time messaging (like web sockets) mechanisms and so on. They are not caused by browser events, so they can’t be tested using Simulate. In such cases you can test your behavious by calling callbacks from component’s properties and component methods by yourself. It is a powerful technique which is worth considering in more ‘unit’ kind of tests. It is by far the only technique that can be used with shallow rendering in an easy way. Everything that can be tested by the Simulate can be tested with this approach as well.

To illustrate this approach a component of an invited person can be used. It is a part of the InvitationList component that was used before to illustrate certain structure testing approaches. The code looks like this:


import React from 'react/addons';
import classNames from 'classnames';

class InvitedPerson extends React.Component {
  constructor(props) {
    super(props);
    this.state = { notSure: false };

    this.toggleConfidenceAboutPerson = this.toggleConfidenceAboutPerson.bind(this);
    this.handleClickNotSureLink = this.handleClickNotSureLink.bind(this);
    this.listElementClasses = this.listElementClasses.bind(this);
    this.renderNotSureLink  = this.renderNotSureLink.bind(this);
  }

  listElementClasses() {
    return classNames({
      ["invited-person"]: true,
      ["not-sure"]: this.state.notSure
    });
  }

  toggleConfidenceAboutPerson() {
    this.setState({ notSure: !this.state.notSure });
  }

  handleClickNotSureLink(ev) {
    ev.preventDefault();
    this.toggleConfidenceAboutPerson();
  }

  renderNotSureLink() {
    let { notSureMessage } = this.props;

    return (
      <p key='notSureBox'>
        <a ref='notSureLink' href='#' onClick={this.handleClickNotSureLink}>
          {notSureMessage[this.state.notSure]}
        </a>
      </p>
    );
  }

  render() {
    let { person } = this.props;

    return (
      <li className={this.listElementClasses()}>
        <p ref='name' key='personName'>{person.name}</p>
        {this.renderNotSureLink()}
      </li>
    )
  }
}

InvitedPerson.defaultProps = {
  notSureMessage: {
    true: "Now I'm sure!",
    false: "I'm not sure about this person"
  }
}

export default { InvitationList, InvitedPerson };

The classNames function is used to perform assigning CSS classes in a more declarative way. It is a function which takes an object with keys being CSS classes, and values being predicates. When a value from a certain key (CSS class) is truthy, the key is added to a returned CSS class list.

This is what test will look like with this approach. To test structure, the shallow rendering approach is used:


import React from 'react/addons';
import { InvitedPerson } from 'components/InvitationList';

describe("Testing InvitedPerson using shallow rendering", () => {
  beforeEach(function() {
    let { TestUtils } = React.addons;

    this.person = { id: 1, name: "Waldo" };

    this.renderer = TestUtils.createRenderer();
    this.renderer.render(<InvitedPerson person={this.person} />);

    this.TestUtils = TestUtils;
  });

  it("is a list element", function() {
    let renderedRoot = this.renderer.getRenderOutput();

    expect(renderedRoot.type).toEqual("li");
  });

  it("has invited-person CSS class", function() {
    let renderedRoot = this.renderer.getRenderOutput();

    expect(renderedRoot.props.className).toEqual("invited-person");
  });

  it("displays a person name", function() {
    let renderedRoot = this.renderer.getRenderOutput();

    expect(renderedRoot.props.children).toContain(<p ref='name' key='personName'>{this.person.name}</p>);
  });


  it("by default assumes the user is sure about this person", function() {
    let renderedRoot    = this.renderer.getRenderOutput();
    let notSureLinkBox  = renderedRoot.props.children[1];

    expect(notSureLinkBox.props.children.props.children).toEqual("I'm not sure about this person");
  });

  it("allows you to toggle confidence about this person", function() {
    let renderedRoot = this.renderer.getRenderOutput();
    let notSureLink = renderedRoot.props.children[1].props.children;

    notSureLink.props.onClick({ preventDefault: () => {} });

    let updatedRoot = this.renderer.getRenderOutput();
    notSureLink = updatedRoot.props.children[1].props.children;

    expect(notSureLink.props.children).toEqual("Now I'm sure!");
  });
});

This example illustrates a problem with lack of tools to traverse React elements - to get my hands on the notSureLink I needed to make a rather ugly traversal by chaining props.children calls. The approach described now is illustrated in a last test case - namely "allows you to toggle confidence about this person". After retrieving notSureLink part of the rendered tree, I’m issuing a props.onClick method call by myself. This is all about this technique.

This technique aligns well with shallow rendering approach, as well as testing integrations like Flux, timers or real-time messaging systems without integrating them in your test at all. All you need to do is to abstract state changes made by these sources in a method and then call it manually in a test. This approach is also great for testing components in an unit way. I’d discourage this approach when testing your components in acceptance way unless you’re forced to use it (state changes are not caused by an user).

Pros:

  • You can use it very fast in your component iterations. When you choose this approach you can even skip the setState approach entirely and extract methods to be called early.
  • It does not have any environment requirements - any JS env would do.
  • It is more powerful technique than testing through Simulate since it can test state changes not caused by an user.
  • Combined with good querying mechanism like refs it can be a great choice to make your tests more concise.

Cons:

  • It is not suited well to acceptance testing. While it’s harder to set impossible state by it than with setState approach, it is still possible.

Repository

There is a repository with examples used in this blogpost and some more examples about testing using various approaches. If you are interested in this field it can be a great learning material - if you feel you’d like to teach us something about testing, don’t be afraid to make contribution to this repo.

Summary

Testing React components can be done in a variety of ways. Each of them have its and cons - but it’s good to know them all and choose the right tool for the job. If I’d be forced to choose the most interesting ones, that’d be a shallow rendering for testing structure and Simulate testing approach for behaviour - but it’s up to you which testing technique you’d like to choose. Like the most concerns around rendering views, React gives you a full freedom here.

comments powered by Disqus