In this tutorial, we will be looking at what snapshot tests are and how we can use snapshot testing to ensure our User Interface does not change without the team knowing about it.
To get started, you will need to familiarize yourself with the following
- NodeJS – A JavaScript runtime built on Chrome’s V8 JavaScript engine.
- React – A JavaScript library for building delightful UI by Facebook
- Jest – A JavaScript testing framework by Facebook.
What Is Snapshot Testing?
Unlike strict Test Driven Development where the standard practice is to write failing tests first then write the code to make the tests pass, Snapshot testing takes a different approach.
To write a snapshot test, you first get your code working, say, a React component, then generate a snapshot of it’s expected output given certain data. The snapshot tests are commited alongside the component and everytime the tests are run. Jest will compare the snapshot to the rendered output for the test.
If the test does not pass, it may mean that there were some unexpected changes on the component that you need to fix, or you made some changes to the component and it’s about time you updated the snapshot tests.
Snapshot testing is meant to be one of many different testing tools. Therefore, you may still need to write tests for your actions and reducers.
Let’s get right into it!
Creating a Simple React Component
To get started, we will create a simple React App using Create React App.
create-react-app ooh-snap
cd ooh-snap
yarn start
We should now have a React app! Let’s go ahead and create a component that we can test. The component that we are going to be creating renders the items
props it receives as either a list or as a span element depending on the number of items.
Create a Components
folder then add the following Items
component
import React from 'react';
import PropTypes from 'prop-types';
/**
* Render a list of items
*
* @param {Object} props - List of items
*/
function Items(props) {
const { items = [] } = props;
if (!items.length) {
// No Items on the list, render an empty message
return <span>No items in list</span>;
}
if (items.length === 1) {
// One Item in the list, render a span
return <span>{items[0]}</span>;
}
// Multiple items on the list, render a list
return (
<ul>
{items.map(item => <li key={item}>{item}</li>)}
</ul>
);
}
Items.propTypes = {
items: PropTypes.array,
};
Items.defaultProps = {
items: [],
};
export default Items;
Finally, let’s update App.js
to render our Component
import React, { Component } from 'react';
import Items from './Components/Items';
class App extends Component {
render() {
const items = [
'Thor',
'Captain America',
'Hulk'
];
return (
<Items items={items} />
);
}
}
export default App;
Effectively, delete App.test.js
because we will be adding our own tests in the next section.
Simple, right? Next, let’s go ahead and add our snapshot tests
Writing Snapshot Tests
To get started, install react-test-renderer, a library that enables you to render React components as JavaScript objects without the need of a DOM.
yarn add react-test-renderer
Great, let’s add our first test. To get started, we will create a test that renders the Items
component with no items passed down as props.
import React from 'react';
import renderer from 'react-test-renderer';
import Items from './Items';
it('renders correctly when there are no items', () => {
const tree = renderer.create(<Items />).toJSON();
expect(tree).toMatchSnapshot();
});
Next, let’s run the tests. Thanks to Create React App, we do not need to set anything else up to run our tests.
yarn test
When your run the tests for the first time, notice that a new snapshot file is created inside a __snapshots__
directory. Since our test file is named Items.test.js
, the snapshot file is appropriately named Items.test.js.snap
that looks like this.
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly when there are no items 1`] = `
<span>
No items in list
</span>
`;
Simple, right? The test matches the component’s exact output. Jest uses pretty-format to make the snapshot files human readable.
If you are getting the hang of it, go ahead and create tests for the two other scenarios where there is one item and where there is multiple items, then run the tests.
...
it('renders correctly when there is one item', () => {
const items = ['one'];
const tree = renderer.create(<Items items={items} />).toJSON();
expect(tree).toMatchSnapshot();
});
it('renders correctly when there are multiple items', () => {
const items = ['one', 'two', 'three'];
const tree = renderer.create(<Items items={items} />).toJSON();
expect(tree).toMatchSnapshot();
});
Next, let’s make updates to our component.
Updating Snapshot Tests
To understand why we need snapshot tests, we’ll go ahead and update the Items
component and re-run the tests. This, for your dev environment, is a simulation of what would happen when someone on your team makes a change to a component and your CI tool runs the tests.
We will add class names to the span
and li
elements, say, to effect some styling.
...
/**
* Render a list of items
*
* @param {Object} props - List of items
*/
function Items(props) {
const { items = [] } = props;
if (!items.length) {
// No Items on the list, render an empty message
return <span className="empty-message">No items in list</span>;
}
if (items.length === 1) {
// One Item in the list, render a span
return <span className="item-message">{items[0]}</span>;
}
// Multiple items on the list, render a list
return (
<ul>
{items.map(item => <li key={item} className="item-message">{item}</li>)}
</ul>
);
}
...
Let’s run our tests again with yarn test
command in lieu of our changes. Notice anything different?
That’s not good, is it? Jest matched the existing snapshots against the rendered component with the updated changes and failed because there were some additions to our compnent. It then shows a diff of the changes that are introduced to the snapshot tests.
To fix this, for whatever reason, would entirely depend on the changes that were introduced to the snapshot tests.
If the changes are not expected, that’s good, you got it well in advance before it was too late. If the changes were expected, update your snapshot tests and everything is green again.
While Jest is in interactive mode, you can update the snapshot tests by simply pressing u
with the options provided. alternatively, you can run jest --updateSnapshot
or jest -u
.
This will update the snapshots to match the updates we made and our tests will effectively pass.
Go ahead and pick into the snapshots folder to see how the snapshot files changed. Here is the updated empty snapshot snippet.
exports[`renders correctly when there is one item 1`] = `
<span
className="item-message"
>
one
</span>
`;
Conclusion
In this tutorial, we have been able to write snapshot tests for a React component. We also updated the component to see the failing tests and eventually update the snapshots to fix the tests.
I hope you can now appreciate how easy it is to iterate and debug your UI changes especially when working as part of a big team.
While we have covered the basics of snapshot tests, there is a lot you could learn on writting better snapshot tests. Do take a look at the Snapshot best practices from the Jest’s documentation to learn more about snapshot testing.
Source: Scotch.io