Best practices for testing React components using Mocha, Chai, and Enzyme

As a data visualization company, our success is crucially dependent on the front-end portion of our product. We subject our whole front-end stack, which is largely based on React, to a thorough arsenal of unit tests, smoke tests, and more.

Today we’d like to share some of the things that we’ve learned from testing React components across multiple projects using an in-house testing stack that we built using Mocha, Chai, Enzyme.

First, a more general lesson.

Don’t be afraid to update your toolchain

It’s easy to get sidetracked by the latest and greatest tools out there, especially in the world of front-end development. You’re usually better off sticking with what works rather than making big investments in shiny new things. But sometimes adopting newer tools and leaving behind more well-established ones can really pay off.

In our case, we made a good move in deciding that Jasmine, the core of our old testing stack, needed to go. Jasmine does have its strengths but we found it to be too slow, too cumbersome to work with, and too heavy on boilerplate to fit our needs. So we decided to survey the ever-expanding galaxy of testing tools and put together something that works better for us.

Our new hybrid testing stack

In place of our previous Jasmine-based stack, we’ve adopted a testing stack consisting of Mocha as our test runner, Chai for assertions, and Enzyme for asserting on component output, properties, and state. This stack has enabled us to write tests that are fast, easy to write, and light on setup boilerplate.

Perhaps most importantly, we’ve standardized on this stack across all of our front-end projects (all of which rely heavily on React). This has enabled us to sharply reduce context switching costs for moving between projects, as our front-end engineers can learn one toolset and use that knowledge anywhere.

Your testing stack might look quite different from ours, but you should work toward standardization if at all possible. Both current and future team members will thank you.

Use shallow rendering whenever possible

Shallow rendering enables you to test a React component very efficiently because only the component and its immediate children is rendered. The other approach, which involves mounting components, doesn’t provide the same isolation and almost always leads to slower, less specific tests.

mount() is still great—and sometimes necessary—but if you’re not careful in use it can easily turn what should be lean and speedy unit tests into bulky integration tests. The point of unit tests is to test an individual unit of work, and Enzyme’s shallow rendering enables us to do precisely that.

Avoid sharing Enzyme wrappers between test cases

Recreating React components in every test case is fairly inexpensive, especially when using shallow rendering. This allows us to isolate our test cases and easily tell what is being tested. The drawback, of course, is duplication of components, but we think that the trade-off is worth it.

Bad

In this example, a single wrapper object is used by two different tests:

const wrapper = shallow(<Thing name={'Steve'} />);

it('should do stuff', () => {
  expect(wrapper).to.doStuff();
});

it('should do different stuff', () => {
  expect(wrapper).to.doDifferentStuff();
});

Good

In this example, two different tests use two (identical) wrapper objects, which makes it immediately clear within the test which wrapper is being tested:

const defaultProps = { name: 'Steve'; };

it('should do stuff', () => {
  const wrapper = shallow(<Thing {...defaultProps} />);
  expect(wrapper).to.doStuff();
});

it('should do different stuff', () => {
  const wrapper = shallow(<Thing {...defaultProps} />);
  expect(wrapper).to.doDifferentStuff();
});

Only pass required props when rendering components

As you saw above, one useful practice we’ve found is defining defaultProps in a describe scope, then reusing those properties among your test cases. A defaultProps object should usually be as bare as you can make it, and it should mirror the required propTypes from the component under test.

Your component should be able to render with just the required props. To assert on this, essentially every React component in our codebase contains the following test:

const defaultProps = {
  dispatch: () => {},
  manifest: { components: [] },
};

it('should render without blowing up', () => {
  const wrapper = shallow(<Thing {...defaultProps} />);
  expect(wrapper.length).to.eql(1);
});

In every other test case, we only pass the props we absolutely need in order for the case to pass. This makes it easy to precisely spot what’s going on as well as which functionality is being tested.

Standardize on a few Chai + Enzyme assertions

Chai has a huge assertions API. This isn’t a bad thing in itself, but it can be hard to keep track of all the possible ways of asserting something, which in turn makes your test code less readable (even if you’re using a tool like chai-enzyme).

To give an example, Here are four different ways to assert on a class name (and there are likely many others):

expect(<Component />).to.have.className('my-class');
expect(<Component />).hasClass('my-class').to.be.true;
expect(<Component />).html().to.contain('class="my-class"');
expect(<Component />).hasClass('my-class').to.eql(true);

You can avoid this by choosing a preferred way to write a given assertion and communicating that to the members of your team (e.g. in your internal documentation).

Conclusion: rejoice that so many tools are available, but adopt them wisely

It’s a great time to be a JavaScript developer. There are so many great new frameworks, tools, libraries, and testing tools popping up all the time. Trying them out is fun, but it’s important to be disciplined about it. Standardizing on some best practices will give you the freedom to experiment with bleeding-edge tools and shiny new objects without compromising your productivity.



Getting started is really easy. Be up and running in minutes. Sign up for free