This tutorial covers:
- Setting up Jest snapshots
- Using snapshots to simulate UI changes for testing
- Using snapshot tests to determine whether changes were intentional
Automated tests are especially important in large applications that have lots of moving parts. It is smart to learn about many methods of testing applications so that you can provide as much coverage as possible. If you are not familiar with using snapshots in testing, read on. Snapshot tests are written as part of frontend test automation.
In this tutorial, I will lead you through using Jest, a JavaScript testing framework, to create snapshots for testing a simple React web application. Using Jest snapshots will help you ensure that your UI changes are deterministic and that you are aware when changes are made. Using that information, you can determine whether the changes were intended or not. You will be using the snapshots you create with Jest to simulate changes in a React application. If constantly changing text assertions are painful, you may find that snapshot testing is a powerful antidote.
Prerequisites
To follow along this tutorial, you’ll need the following:
- NodeJS installed locally.
- A GitHub account.
- A CircleCI account.
- Basic knowledge of JavaScript, React, Git and Jest.
Our tutorials are platform-agnostic, but use CircleCI as an example. If you don’t have a CircleCI account, sign up for a free one here.
Snapshot testing in Jest
Jest is a JavaScript testing framework that makes writing frontend tests like snapshots, unit tests, and component tests easy and efficient. Snapshot testing is a type of output comparison testing. This type of testing ensures that your application adheres to the quality characteristics and code values of your development team.
How a snapshot test works
Snapshot tests are useful when you want to make sure your UI does not change unexpectedly. A typical snapshot test case renders a UI component, takes a snapshot, then compares it to a reference snapshot file stored alongside the test. The test compares the current state of your application to the established snapshots and expected behavior.
Visualizing the snapshot testing process
The following image illustrates the process of snapshot testing in Jest. It shows the different outcomes when a snapshot passes, when a snapshot test fails, and what actions take place.
Setting up the sample React app
In this tutorial, your application will consist of a simple React component with two buttons that increment and decrement the count when clicked.
Clone the repository by running this command in your terminal:
git clone https://github.com:mwaz/jest-snapshot-testing.git;
cd jest-snapshot-testing;
Next, you will need the following dependencies installed from the npm React testing library, Jest, and the React test renderer.
They are installed in your package.json
file. To install them in your system, open your terminal and run:
npm install
Once the dependencies are installed, run the application using this command:
npm start
This starts your application.
Your test will determine whether the counter value initializes at zero, and also whether the buttons work. Because the value changes depending on the button that is clicked, you may want to know that the incremental or decremental value stays the same, always increasing or decreasing by one. You can now start writing your tests.
Writing snapshot tests
Jest uses regular expressions to look for a file with the .test.js
or .test.jsx
extensions. Once the test files with these extensions are encountered, Jest will automatically run tests in those files when the test command is executed.
To write your first snapshot test, you will use the renderer
module. This module renders the Document Object Model (DOM) element that will be saved as the text snapshot:
import renderer from "react-test-renderer";
Write your test to ensure that it captures the render of the <App>
component and saves it as a Jest snapshot. This is the structure for the test:
import React from "react";
import renderer from "react-test-renderer";
import App from "./App";
describe("Jest Snapshot testing suite", () => {
it("Matches DOM Snapshot", () => {
const domTree = renderer.create(<App />).toJSON();
expect(tree).toMatchSnapshot();
});
});
The test has the domTree
variable, which holds the DOM tree of the rendered component in JSON format. This makes it easier to save and compare snapshots.
expect(domTree).toMatchSnapshot()
creates a snapshot if it does not exist, saves it, and checks if a snapshot is consistent with previous stored snapshots. If there is an existing snapshot, Jest compares the two snapshots. If they match, the test passes. Snapshots that do not match cause the test to fail. The test also uses the .toJSON()
method, which returns a JSON object of the rendered DOM tree snapshot.
Once you run the test (using the command npm test
), there will be a new folder called (__snapshots__)
with the file App.test.js.snap
inside it. The file contains the saved snapshot, which should be similar to this snippet:
exports[`Jest Snapshot testing suite matches snapshot 1`] = `
<div
className="App"
>
<div
className="counter"
>
<div
className="buttons"
>
<button
onClick={[Function]}
>
Increment
</button>
<button
onClick={[Function]}
>
Decrement
</button>
</div>
<p>
0
</p>
</div>
</div>
`;
This snapshot file shows how the DOM tree of the component looks, including the parent selector elements and the child elements.
To better understand snapshots, open the elements
section in the tab where your React application is running. Compare it side by side with the snapshot; they should be almost identical. Snapshots have a structure similar to the DOM, which makes the process of identifying changes to the DOM seamless.
The fact that text snapshots are created from the DOM means they can fail only when the DOM has changed or has content that differs from what was present when the snapshot was taken. Next, you will investigate how changes happen in the DOM, how they trigger snapshot changes, and how to handle this process.
Handling snapshot changes
Now that you know how snapshots are created, it is time to learn more about when they fail and why. To demonstrate this, use the previous test and make changes to your DOM tree. The change you will make is introducing a title <h1>COUNTER</h1>
to the component. This addition is shown in the file Counter.js
:
return (
<div className="counter">
<h1>COUNTER</h1>
<div className="buttons">
<button onClick={this.increment}>Increment</button>
<button onClick={this.decrement}>Decrement</button>
</div>
<p>{this.state.count}</p>
</div>
);
After making this change, run the test again. The test should fail.
Because these changes are expected, you will need to update your existing snapshot instead of changing the code to match the previous snapshot. Select the u option to update the snapshot when Jest is in watch mode
. Updating the snapshot tells Jest that the changes were intentional and that you want to keep. After the snapshot update is triggered, your test is back to being happy and it passes beautifully.
Note: When Jest is in watch mode
, the application is being tracked, so any changes will trigger a rerun of the tests. To activate watch mode
, specify it in Jest runs with the --watch
argument.
PASS src/App.test.js (20.905 s)
Jest Snapshot testing suite
√ Matches Snapshot (64 ms)
> 1 snapshot updated.
Snapshot Summary
> 1 snapshot updated from 1 test suite.
↳ src/App.test.js
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 1 updated, 1 total
Time: 51.348 s
Ran all test suites related to changed files.
Adding more snapshot tests
You can add more snapshot tests to make sure that all important visual elements in your application are consistent with your UI specifications and UX guidelines and that everything in your application works correctly. Snapshot tests are part of comprehensive frontend testing that should also include unit and component tests.
For this tutorial, I will run through adding just one more snapshot test. This test checks that the increment functionality works as intended. This code snippet can be found in the file App.test.js
:
it("Should render 3 after three increments", () => {
const component = renderer.create(<Counter />);
component.getInstance().increment();
component.getInstance().increment();
component.getInstance().increment();
expect(component.toJSON()).toMatchSnapshot();
});
In this test, the counter component structure is saved to a component variable. The test then accesses the increment()
method of your class-based component
and calls it three times. The goal is to make sure that when the Increment
button is clicked three times, the count rendered is three. This information is saved to the snapshot, which should pass.
Integrating snapshot testing with CircleCI
Consider this: why run successful tests that no one else knows about? Instead, take a moment to share your tests with the rest of the team, so that they can also benefit from the insights they provide.
For this tutorial, I will lead you through the steps for using CircleCI to execute your snapshot tests.
In the root folder of your application, create a .circleci
folder, and add a config.yml
file. The file will contain all the configuration required for running your CircleCI pipelines.
In the CircleCI config.yml file, add this configuration:
version: 2.1
jobs:
build:
working_directory: ~/repo
docker:
- image: cimg/node:14.17.1
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "package-lock.json" }}
- run:
name: install dependencies
command: npm install
- save_cache:
key: dependency-cache-{{ checksum "package-lock.json" }}
paths:
- ./node_modules
- run:
name: Jest snapshot tests
command: npm test
- store_artifacts:
path: ~/repo/jest-snapshot-testing
Pushing your changes to GitHub
If you cloned the repository the changes already exist, making this an option step. However, if you are using a different repository and using the same configuration, you will need to push the changes to the repository.
Save this file, commit and push your changes to your GitHub repository. After navigating to the CircleCI dashboard, click Set up Project beside the repository name.
When prompted, select main
, which is your default branch. Then click Set Up Project. Your project will begin running on CircleCI.
There should be a green build on the CircleCI dashboard. Click it to review the build details.
Fantastic! Your builds are green and all your tests executed successfully.
Conclusion
In this tutorial, you have learned about snapshot testing and how useful it is in making sure your UI looks and works as intended. You learned how to write a snapshot test, and used a snapshot as a comparison to ensure that any changes made were intended. You also learned how to update snapshots in case there are intentional changes. Finally you integrated CircleCI to run your tests. I hope you have enjoyed working on the project for this tutorial. Until next time, keep on coding!
Waweru Mwaura is a software engineer and a life-long learner who specializes in quality engineering. He is an author at Packt and enjoys reading about engineering, finance, and technology. You can read more about him on his web profile.