Unit testing React components without a DOM

When unit testing React components the common approach has been to render them into a DOM (with something like jsdom) and run some assertions against them with the help of the React TestUtils.

This has changed in 0.13 where an early implementation of shallow rendering is now ready to use.

Shallow rendering

Instead of rendering into a DOM the idea of shallow rendering is to instantiate a component and get the result of its render method, which is a ReactElement. From here you can do things like check its props and children and verify it works as expected.

As you can imagine this is much faster (and less hassle) and will be the recommended way to test components in the future.

How it works

All you need to do is create an instance of the shallow renderer, render your component and then grab the output.

const React = require('react/addons');
const TestUtils = React.addons.TestUtils;

const shallowRenderer = TestUtils.createRenderer();
shallowRenderer.render(React.createElement(MyComponent, { className: 'MyComponent' }, 'some child text'));

const component = shallowRenderer.getRenderOutput();

This gives you an object that represents your component and looks roughly like the below (I’ve omitted some properties for the sake of brevity)

{
  "type": "div",
  "_store": {
    "props": {
      "className": "MyComponent",
      "children": [{
        "type": "h2",
        "_store": {
          "props": {
            "className": "MyComponent-header",
            "children": "Title"
          },
          "originalProps": {
            "className": "MyComponent-header",
            "children": "Title"
          }
        }
      }]
    }
  }
}

And now you can test against it

expect(component.props.className).to.equal('MyComponent');

Reusing the shallowRenderer

Once you start using shallow rendering there will be a need to create rendered versions of components in each test, so it makes sense to move the logic into a reusable module. I’ve found a createComponent function works nicely, but you’re free to name this whatever you wish.

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

export default createComponent;

function createComponent(component, props, ...children) {
  const shallowRenderer = TestUtils.createRenderer();
  shallowRenderer.render(React.createElement(component, props, children.length > 1 ? children : children[0]));
  return shallowRenderer.getRenderOutput();
}

Also on GitHub as a gist.

Now it can be used in your unit tests the same way as React.createElement.

import createComponent from '../util/create-component';

component = createComponent(MyComponent, { className: 'MyComponent' }, 'some child text');

Example: Testing a Post component

Now that the basics have been explained let’s work through a simple example of testing a fictional Post component. It will accept a title and content and ensure any paragraph tags on the content are stripped away:

import React from 'react';

const { div, h2, p } = React.DOM;

export default React.createClass({
  displayName: 'Post',

  propTypes: {
    title: React.PropTypes.string.isRequired,
    content: React.PropTypes.string
  },

  stripParagraphTags(html) {
    return html.replace(/<\/?p>/g, '');
  },

  render() {
    const content = this.stripParagraphTags(this.props.content);

    return  div({ className: 'Post'},
              h2({ className: 'Post-header' }, this.props.title),
              p({ className: 'Post-content'}, content)
            );
  }
});

The Post component spec

I’ve chosen to use Chai and Mocha for my unit tests, but you’re free to use Jest, Jasmine or any other test runner and assertion library.

First we’ll set up some boilerplate before we start writing actual tests

import { expect } from 'chai';

import Post from '../../components/post.react';
import createComponent from '../util/create-component';

describe('Post component', function() {
  let post;

  beforeEach(function() {
    post = createComponent(Post, { title: 'Title', content: '<p>Content</p>'});
  });
});

This is all that is needed. Wonderfully simple.

When we looked at the structure of the object returned from the shallow renderer you may have noticed the children property. This will contain any text, DOM elements or other React components that might make up the component being tested.

In this example the children property is an array that contains our title and content, so we can simply check that our props are being rendered as expected.

it('should render a post title and content', function() {
  const postTitle = post.props.children[0];
  const postContent = post.props.children[1];

  expect(postTitle.props.children).to.equal('Title');
  expect(postContent.props.children).to.equal('Content');
});

To verify this we can run Mocha with Babel to take care of the ES6 compilation.

mocha test/components --compilers js:babel/register

  Post component
    ✓ should render a post title and content


  1 passing (12ms)

Great, our first test is passing.

Testing component methods

It’s not uncommon to have a few methods attached to the React component and to need to test them. An example might be a method that performs some complex transforms on data sent in via the props.

If you stick to stateless methods on your React components (a good pattern to adhere to regardless) then it’s an absolute breeze to unit test them.

You can reference any method directly on the prototype of the component. Let’s make sure the stripParagraphTags method is working correctly.

describe('stripParagraphTags method', function() {
  it('should strip <p> tags', function() {
    const strippedText = Post.prototype.stripParagraphTags('<p>Some text.</p> <p>More text.</p>');

    expect(strippedText).to.equal('Some text. More text.');
  });
});
  Post component
    ✓ should render a post title and content
    stripParagraphTags method
      ✓ should strip <p> tags


  2 passing (29ms)

Great.

Rendering a list of Posts

Now that the Post component is rendering and passing the unit tests we recieve a requirement to render a list of them from a set of data and ensure that it works as expected.

To do this we’ll reach for another React component called PostList and it will just be responsible for taking a set of data and rendering a Post for each item of data.

Keeping components separated like this is can allow reuse in different contexts and ensure components do one job well. Composition is encouraged and is one of Reacts’ strengths.

The PostList component

Nothing too fancy here, just creating a new Post component for each item in the posts data set that is passed in as a prop.

import React from 'react';
import Post from './post.react';

const { ul, li } = React.DOM;

export default React.createClass({
  displayName: 'PostList',

  renderListItems(posts) {
    return posts.map((post) => {
            return li({ key: post.id, className: 'PostList-item' },
              React.createElement(Post, { title: post.title, content: post.content })
            );
          });
  },

  render() {
    return ul({ className: 'PostList'}, this.renderListItems(this.props.posts));
  }
});

In terms of what to test here it seems sensible to just make sure the PostList has rendered a Post for each data item. With the above code we could expect an HTML output like this:

<ul class="PostList">
  <li class="PostList-item">
    <div class="Post"><!-- content --></div>
  </li>
  <!-- And repeat... -->
</ul>

With that in mind we will write a spec to ensure each <li> contains a Post component and that the total matches the total set of posts in the data source.

import { expect } from 'chai';

import PostList from '../../components/post-list.react';
import createComponent from '../util/create-component';

describe('PostList component', function() {
  const postData = [
    { id: 1, title: 'Title 1', content: '<p>Content 1</p>' },
    { id: 2, title: 'Title 2', content: '<p>Content 2</p>' },
    { id: 3, title: 'Title 3', content: '<p>Content 3</p>' }
  ];

  it('should render a list of post components', function() {
    const postList = createComponent(PostList, { posts: postData });
    const items = postList.props.children.filter(postListItem => postListItem.props.children.type.displayName === 'Post');

    expect(items.length).to.equal(postData.length);
  });
});

The line of interest here is where we check the child elements of PostList:

const items = postList.props.children.filter(postListItem => postListItem.props.children.type.displayName === 'Post');

First we filter through the children of PostList (which are the <li> elements) and then within each of those we can check the child has a displayName of Post. If it is then the component is returned. Now the length of the items array should match the total items in the postData array.

  PostList component
    ✓ should render a list of post components

  Post component
    ✓ should render a post title and content
    stripParagraphTags method
      ✓ should strip <p> tags


  3 passing (16ms)

And it does! Perfect.

Testing the actual rendering

Areas of testing that this doesn’t cover would be interactions with components (form fields, buttons etc) and visual rendering. I find this sort of acceptance testing is best left to tools like Browserstack or Sauce Labs as they can test multiple devices and operating systems and will paint a more accurate picture of how your application behaves.

I’ve found unit testing with shallow rendering is best used to ensure the application data is passing through your components as intended but you can make them as granular as you like.

Conclusion and example code

We’ve now written and unit tested two separate React components without even needing a DOM or web browser. Although it’s still an experimental feature I would recommend trying it out and seeing how it fits in with your code base.

You can grab all the example code in this repository on GitHub and find more information on the React documentation page.

About

Hi, I build things with code, usually consisting of HTML5, CSS & JavaScript

Sometimes I write about it too