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.