How to Test React Components

Tue Jun 13, 2017 - 1000 Words

Testing software is incredibly important to ensure that bugs don’t get introduced over time, but they can also help us design our software. Now, before we get too far into building our application, we can explore testing with React.

Goals

  • Install enzyme to help us test our React components
  • Add automated tests around our ChordEditor component.

Since we have a custom component that isn’t yet too complicated, now is the perfect time for us to add tests to it. It can sometimes be overwhelming to add tests after the fact.

Running Our Existing Test

There is already a test in our application for our App, so before we do anything else we will run that to see how the test output looks. By default the npm test and yarn test commands will run in a watcher mode so that tests will run automatically, but in this case, we just want to run them all. Thankfully, if you set the CI environment variable to true the command will do just that. Let’s run our tests now:

$ CI=true npm test
> chords@0.1.0 test /Users/kthompson/Dropbox/code/coderjourney/learn-react/chords
> react-scripts test --env=jsdom

 PASS  src/App.test.js
  ✓ renders without crashing (33ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.854s, estimated 1s
Ran all test suites.

Let’s take a look at that test before moving on:

src/App.test.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

it('renders without crashing', () => {
  const div = document.createElement('div');
  ReactDOM.render(<App />, div);
});

There isn’t a whole lot going on here since we’re not even making an assertion, but you can see that React itself doesn’t crash when we try to render our parent component.

Installing Enzyme

We’re going to be using enzyme from AirBnb as an extra part of our testing setup to make working with components a little easier. The jest package is also being used as the default test runner for React applications. Jest maintained by Facebook and create-react-app set it up for us when we generated our application. Let’s install enzyme now (along with react-addons-test-util):

$ npm install --save-dev react-addons-test-util enzyme

Note: In prepping this tutorial I had some issues with npm 5.0.3 installing things properly. In my case, I cleared the npm cache and removed the chords/node_modules directory before running npm install again.

Writing Tests for the ChordEditor Component

Now that we have our testing libraries installed and we know that our suite can run it’s time to write some tests. The ChordEditor component does a few different things that we can test in isolation. Let’s start with a few tests about the general structure of our component’s output:

src/components/ChordEditor.test.js

import React from 'react';
import { shallow } from 'enzyme';
import ChordEditor from './ChordEditor';

describe('<ChordEditor />', () => {
  it('renders an editor area', () => {
    const editor = shallow(<ChordEditor />);
    expect(editor.find('textarea').length).toEqual(1);
  });

  it('renders an output area', () => {
    const editor = shallow(<ChordEditor />);
    expect(editor.find('div.chord-output').length).toEqual(1);
  });
});

Notice that our import statements are a little bit different this time around because we’re using shallow from enzyme. The shallow function prevents child components from being processed, so we don’t need to worry about doing more work than necessary in our tests. We’ve also wrapped all of the tests in a describe function for some added structure.

These aren’t the most useful tests in the world because we’re just asserting that we have some basic content. We can ensure that our code is rendering our content properly using tests.

src/components/ChordEditor.test.js

  // Earlier tests omitted

  it('generates the chord chart output', () => {
    const editor = shallow(<ChordEditor />);
    const expectedOutput =
      '<table>' +
      '<tr>' +
      '<td class="chord"></td>' +
      '</tr>' +
      '<tr>' +
      '<td class="lyrics">Type some lyrics here </td>' +
      '</tr>' +
      '</table>';

    const realOutput = editor.find('div.chord-output').html();

    expect(realOutput.indexOf(expectedOutput) > -1).toEqual(true);
  });

  it('regenerates the chord chart output based on the state', () => {
    const editor = shallow(<ChordEditor />);
    const expectedOutput =
      '<table>' +
      '<tr>' +
      '<td class="chord">B</td>' +
      '<td class="chord">Am</td>' +
      '</tr>' +
      '<tr>' +
      '<td class="lyrics">New </td>' +
      '<td class="lyrics">Lyrics </td>' +
      '</tr>' +
      '</table>';

    editor.setState({ value: "[B]New [Am]Lyrics" });

    const realOutput = editor.find('div.chord-output').html();
    expect(realOutput.indexOf(expectedOutput) > -1).toEqual(true);
  });
  // closing of `describe` omitted

Now if we run these we should see that they’re all passing:

$ CI=true npm test
> chords@0.1.0 test /Users/kthompson/Dropbox/code/coderjourney/learn-react/chords
> react-scripts test --env=jsdom

 PASS  src/components/ChordEditor.test.js
  ● Console

    console.warn node_modules/react-addons-test-utils/index.js:33
      Warning: ReactTestUtils has been moved to react-dom/test-utils. Update references to remove this warning.
    console.error node_modules/fbjs/lib/warning.js:36
      Warning: Shallow renderer has been moved to react-test-renderer/shallow. Update references to remove this warning.

 PASS  src/App.test.js

Test Suites: 2 passed, 2 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        1.007s
Ran all test suites.

Note: We’re going to ignore those console lines for the moment.

Since we used dangerouslySetInnerHtml we can’t use .find to go deeper into the content because we told React to not worry about the HTML rendered within our chord-output element. Our tests do show that we’re getting some different HTML output based on the state of our component, but they also show us that we can’t control the content that is being shown by default. In the next tutorial, we will refactor our component to make it easier to specify what is going to show on the first render.

Recap

In this tutorial, we added some simple tests and some more involved ones to ensure that our ChordEditor is doing what we expect. By using enzyme we were able to utilize functions like shallow, find, and html to interact with our component, and jest provided the structure and runner for our tests.