w3resource

Snapshot Testing


Snapshot tests are a useful tool when you want to make sure your UI doesn't change unexpectedly.

A typical snapshot test case for a mobile app will render a UI component, it will take a snapshot, then compares it to a reference snapshot file that is stored alongside the test. The test fails if the two snapshots do not match: either when the change is unexpected, or when the reference snapshot needs to be updated to the new version of the UI component.

Snapshot Testing with Jest

You can follow a similar approach when testing your React components. Rather than rendering the graphical UI, which would require that you build the entire app, you can however use a test renderer to quickly generate a serializable value for your React tree. Consider the example test for a simple Link component as shown below:

import React from 'react';
import Link from '../Link.react';
import renderer from 'react-test-renderer';

it('renders correctly', () => {
  const tree = renderer
    .create(<Link page="http://www.newsapp.com">NewsApp</Link>)
    .toJSON();
  expect(tree).toMatchSnapshot();
});

The first time you run this test, Jest will create a snapshot file that looks like this:

exports[`renders correctly 1`] = `
<a
  className="normal"
  href="http://www.newsapp.com"
  onMouseEnter={[Function]}
  onMouseLeave={[Function]}
>
  NewsApp
</a>
`;

The snapshot artifact needs to be committed alongside code changes, and should reviewed as part of your code review process. Jest will use pretty-format to make snapshots human-readable during code review. For subsequent test runs, Jest simply compares the rendered output with the previous snapshot. In the case where they match, the test passes. In the case where they don't match, it is either the test runner found a bug in your code (in this case, it's <Link> component) that should be fixed, or it is because the implementation has changed and the snapshot needs to be updated.

Note: The snapshot will be directly scoped to the data you render - in our example it is the <Link /> component with page prop passed to it. This will imply that even if any other file has missing props (Say, App.js) in the <Link /> component, it still passes the test as the test doesn't know the usage of <Link /> component and it is scoped only to the Link.react.js. Also, Rendering the same component with different props in other snapshot tests won't affect the first one, as the tests do not know about each other.

Updating Snapshots

Spotting when a snapshot test fails after a bug has been introduced is straightforward. When this happens, you should go ahead and fix the issue and make sure your snapshot tests are passing again. Now, let's discuss the case when a snapshot test is failing due to an intentional implementation change.

One of such situation is when you intentionally change the address the Link component in your example is pointing to.

// Updated the test case with a Link to a different address
it('renders correctly', () => {
  const tree = renderer
    .create(<Link page="http://www.instagram.com">Instagram</Link>)
    .toJSON();
  expect(tree).toMatchSnapshot();
});

In this case, Jest will print the output below:

[[SnapshotFailed]]

Because you just updated our component to point to a different address, you ought to expect changes in the snapshot for this component. your snapshot test case fails because the snapshot for your updated component no longer matches the snapshot artifact for this test case.

For you to resolve this, you have to update our snapshot artifacts. You can run Jest with a flag that tells it to re-generate snapshots:

jest -updateSnapshot

Run the command above and accept the changes. You can also use the equivalent single-character -u flag to re-generate snapshots if you prefer. This re-generates snapshot artifacts for all failing snapshot tests. If you had any additional failing snapshot tests due to an unintentional bug, you would need to fix the bug before re-generating snapshots to avoid recording snapshots of the buggy behavior.

If you would like to limit which snapshot test cases get re-generated, you can pass an additional --testNamePattern flag to re-record snapshots only for those tests that match the pattern.

Interactive Snapshot Mode

You can update failed snapshots interactively in watch mode:

[[interactiveSnapshotMode.png]]

Once you are in Interactive Snapshot Mode, Jest steps you through the failed snapshots one test at a time and gives you the opportunity to review the failed output.

Then you can choose to update that snapshot or you can skip to the next:

Inline Snapshots

Inline snapshots behave identically to the external snapshots (.snap files), except that the snapshot values are written automatically back into the source code. What this means is that you can get the benefits of automatically generated snapshots without having to switch to an external file to make sure the correct value was written.

Prettier powers inline snapshots. If you want to use inline snapshots you must have prettier installed in your project. Your Prettier configuration is respected when writing to test files.

If you have prettier installed in a location where Jest cannot find it, you can instruct Jest how to find it using the "prettierPath" configuration property.

Example:

First, you have to write a test, calling the .toMatchInlineSnapshot() method with no arguments:

it('renders correctly', () => {
  const tree = renderer
    .create(<Link page="https://prettier.io">Prettier</Link>)
    .toJSON();
  expect(tree).toMatchInlineSnapshot();
});

When next you run Jest, tree is evaluated, and a snapshot will then be written as an argument to toMatchInlineSnapshot:

it('renders correctly', () => {
  const tree = renderer
    .create(<Link page="https://prettier.io">Prettier</Link>)
    .toJSON();
  expect(tree).toMatchInlineSnapshot(`
<a
  className="normal"
  href="https://prettier.io"
  onMouseEnter={[Function]}
  onMouseLeave={[Function]}
>
  Prettier
</a>
`);
});

It is as easy as that! You can even choose to update the snapshots with --updateSnapshot or by using the u key in --watch mode.

Property Matchers

Often there are certain fields in the object you want to snapshot which are generated (like IDs and Dates). If you try snapshotting these objects, they force the snapshot to fail on every run:

it('will fail every time', () => {
  const user = {
    createdAt: new Date(),
    id: Math.floor(Math.random() * 20),
    name: 'LeBron James',
  };

  expect(user).toMatchSnapshot();
});

// Snapshot
exports[`will fail every time 1`] = `
Object {
  "createdAt": 2018-05-19T23:36:09.816Z,
  "id": 3,
  "name": "LeBron James",
}
`;

For such cases, Jest allows you to provide an asymmetric matcher for any property. These matchers will be checked before the snapshot is written or tested, and then they will saved to the snapshot file instead of the received value:

it('will check the matchers and pass', () => {
  const user = {
    createdAt: new Date(),
    id: Math.floor(Math.random() * 20),
    name: 'LeBron James',
  };

  expect(user).toMatchSnapshot({
    createdAt: expect.any(Date),
    id: expect.any(Number),
  });
});

// Snapshot
exports[`will check the matchers and pass 1`] = `
Object {
  "createdAt": Any<Date>,
  "id": Any<Number>,
  "name": "LeBron James",
}
`;

Any given value that is not a matcher will be exactly checked and saved to the snapshot:

it('will check the values and pass', () => {
  const user = {
    createdAt: new Date(),
    name: 'Bond... James Bond',
  };

  expect(user).toMatchSnapshot({
    createdAt: expect.any(Date),
    name: 'Bond... James Bond',
  });
});

// Snapshot
exports[`will check the values and pass 1`] = `
Object {
  "createdAt": Any<Date>,
  "name": 'Bond... James Bond',
}
`;

Best Practices

Just like any testing strategy, there are best-practices you should be aware of, and guidelines you need to follow, in order for you to use them effectively.

1. Treat snapshots as code

You should commit snapshots and then review them as part of your regular code review process. This means that you should treat snapshots as you would any other type of test or code in your project.

You should ensure that your snapshots are readable by keeping them focused, short, and by using tools that enforce these stylistic conventions.

As we mentioned previously, Jest uses pretty-format to make snapshots human-readable, but you can find it useful to introduce additional tools, such as eslint-plugin-jest with its no-large-snapshots option, or introducing snapshot-diff with its component snapshot comparison feature, these will promote committing short, focused assertions.

The goal here is to make it easy to review snapshots in pull requests, and fight against the habit of simply regenerating snapshots when test suites fail instead of examining the root causes of the snapshots failure.

2. Tests should be deterministic

Your tests need to be deterministic. Running the same tests multiple times on a component that has not changed is expected to produce the same results every time. It is your responsibility to make sure your generated snapshots do not include platform specific or other non-deterministic data.

For instance, if you have a Clock component that makes use Date.now(), the snapshot generated from this component is different every time the test case is run. In this case you can mock the Date.now() method to return a consistent value every time the test is run:

Date.now = jest.fn(() => 1482363367071);

Now, each time the snapshot test case runs, Date.now() returns 1482363367071 consistently. This results in the same snapshot being generated for this component regardless of when the test is run.

3. Use descriptive snapshot names

Always use descriptive test and/or snapshot names for snapshots. The best names are the ones that describe the expected snapshot content. This will make it easier for reviewers to verify the snapshots during review, and to help anyone to know whether or not an outdated snapshot is the correct behavior before updating.

For instance, compare:

exports[`<UserName /> should handle some test case`] = `null`;

exports[`<UserName /> should handle some other test case`] = `
<div>
  Yuksek Galip
</div>
`;

To:

exports[`<UserName /> should render null`] = `null`;

exports[`<UserName /> should render Yuksek Galip`] = `
<div>
  Yuksek Galip
</div>
`;

Since the later will describe exactly what is expected in the output, it's easy to see when it is wrong:

exports[`<UserName /> should render null`] = `
<div>
  Yuksek Galip
</div>
`;

exports[`<UserName /> should render Yuksek Galip`] = `null`;

Frequently Asked Questions

Are snapshots written automatically on Continuous Integration (CI) systems?

No, as from Jest 20, snapshots in Jest will not be automatically written when Jest is run in a CI system without explicitly passing --updateSnapshot. It is expected that all your snapshots are part of the code that is run on CI and since new snapshots will automatically pass, they should not pass a test that is run on a CI system. It is recommended that you always commit all snapshots and to keep them in version control.

Should snapshot files be committed?

Yes, all snapshot files need to be committed alongside the modules they are covering and their tests. They have to be considered part of a test, similar to the value of any other assertion that is in Jest. In fact, snapshots will represent the state of the source modules at any given point in time. In this way, when you modify the source modules, Jest will be able to tell what changed from the previous version. It can also provide a lot of additional context during code review in which the reviewers can study your changes better.

Does snapshot testing only work with React components?

React and React Native components are a very good use case for snapshot testing. However, snapshots will be able to capture any serializable value and has to be used anytime the goal is testing whether the output is correct.

What's the difference between snapshot testing and visual regression testing?

Snapshot testing and visual regression testing are two very distinct ways of testing UIs, and they both serve different purposes. Visual regression testing tools collect screenshots of web pages and then compare the resulting images pixel by pixel. While with Snapshot testing values are serialized, stored within text files, and then compared using a diff algorithm.

Does snapshot testing replace unit testing?

Snapshot testing is just one of more than 20 assertions that ship with Jest. The goal of snapshot testing is not to replace existing unit tests, but to provide additional value and thus make testing painless. There are scenarios when snapshot testing can potentially remove the need for unit testing for a particular set of functionalities (e.g. React components), but both of them can work together as well.

What is the performance of snapshot testing regarding speed and size of the generated files?

Jest has been rewritten with performance as a focus, and snapshot testing is not an exception to this idea. Since snapshots are being stored inside of text files, it is fast and reliable. Jest will generate a new file for each test file that invokes the toMatchSnapshot matcher. The size of the snapshots is quite small: For reference, the size of all snapshot files inside the Jest codebase itself is less than 300 KB.

How do I resolve conflicts within snapshot files?

Snapshot files always have to represent the current state of the modules they are covering. Therefore, when you are merging two branches and you encounter a conflict in the snapshot files, you can either choose to resolve the conflict manually or you update the snapshot file by running Jest and inspecting the result.

Is it possible to apply test-driven development principles with snapshot testing?

Although you can write snapshot files manually, it is usually not approachable. Snapshots will help you to figure out whether the output of the modules covered by tests is changed, instead of giving guidance to design the code in the first place.

Does code coverage work with snapshot testing?

Yes, it does, just as with any other test.



Inviting useful, relevant, well-written and unique guest posts