How to make great snapshot tests with Jest

Marcus Stamström
7 min readFeb 11, 2019

Snapshots can be very powerful, lets figure out how!

Unit testing has improved a lot over recent years in frontend. However, we rarely test our template logic and this is what snapshot testing solves. Snapshot testing have received most positive, but also some negative reviews since its release and I will try to address some of these negative concerns. We will start with going through how snapshot testing works with Jest and then go through how to avoid these pitfalls and make full use of snapshot testing.

What is a snapshot test?

Snapshot testing is a tool that makes sure our UI doesn’t change unexpectedly. We render a certain view and take a reference snapshot of that view. We can then later compare our reference snapshots to the updated snapshot of the same view and thereby making sure that our view stays consistent as we keep developing our app.

Snapshot testing with Jest

With Jest, we can snapshot our component without ever render it in the browser. Instead we use a test renderer to create a serializable value of our component tree. When we have our serializable component tree, we can then get a JSON from the output our component tree. We can also set which state we want the component to be in before taking the snapshot. Having it as JSON makes the assertion fast. Fast enough to be used for unit test of the component.

Below will follow examples of how snapshot testing works using React. Snapshot testing also works for other web component frameworks such as VueJs and Angular. The difference between snapshot testing in these different frameworks is that we need a test renderer specific for the framework we use.

Example of snapshot test

In this example we have a AddTodo component, where we want to test that the render function returns the correct output. So we create the component with react-test-renderer and then do the snapshot assertion. Each time we run this test, toMatchSnapshot() will take a JSON of our component tree and assert it against our reference snapshot. toMatchSnapshot() is an assertion, just like for example toBe(), with the difference that toMatchSnapshot() does some pre-handling of our snapshots.

Each snapshot will get the name of it’s test description, so it’s important to have good test description. If Jest can’t find any snapshot with a matching name, it will create a new reference snapshot with the test description as it’s name. If Jest finds a snapshot with a matching test description, it will do the assertion against that reference snapshot.

So usually, first time we run our test we create our reference snapshot and the other times we assert our reference snapshot against our created snapshot in our test run. If you would to change the test description, Jest will create a new reference snapshot and the old snapshot will become obsolete.

Snapshot with setState

With react-test-renderer, we also get the component instance on the created component. With this instance we can both setState and run methods on our component. We can also take several snapshots in each test, so in my example a snapshot after each action, to make sure that the UI is correct during each stage of the test. Also note that all snapshots ends with a digit and that digit will increase for each toMatchSnapshot() we run in the test.

Updating snapshots

Since our UI will change from time to time, we will need to update the snapshots. Jest has easy commands for this. To update all your snapshots, run:

jest --updateSnapshot

But if you don’t run your tests often, there can sometimes be a lot of failing snapshot tests if we changed the UI in several places. In these cases it will be hard to get an overview of which snapshots that should be updated and where the UI has changed unexpectedly.

For these cases, when you run Jest in watch mode, you can run the interactive command. This lets you update one snapshot at the time and gives you full control over which snapshots that should be updated.

Concerns of snapshot testing

I personally like snapshot testing, however its very easy to create a snapshot test without gaining any test quality. So below is a list of main concerns I’ve found about snapshot testing.

Snapshot tests are hard to understand

One concern is that snapshots can become very large, especially if we have a large component tree. When snapshots become large they are hard to understand, difficult to read and maintain. They also become fragile and will easily break. Snapshots become large mainly for two reasons: the components template is large or that the whole component tree is in the snapshot.

Snapshot tests don’t encode the developers intention

Good tests encode the developers intention. By looking at a test, you as a developer should understand what the test does and how the code works. Snapshots fail to express the authors intent of what the test does and how the code works.

Snapshot files aren’t code reviewed

Generated files are usually not reviewed before committing them. Leading to having false snapshots and when they later fail, the developer will just update them instead of putting time into figuring out why the snapshot test fail.

Key pieces to make snapshot testing work

As I said I think snapshot testing solves how to test templates. If we don’t use snapshot testing, the other alternative would be query selectors and I think snapshot testing is much more efficient. Here are my key pieces to make full use of snapshot testing.

Unit test with snapshots

Sometimes when we create our snapshots we don’t mock the child components, making our test more of a integration test than a unit test. I agree that if we create the whole component tree, the snapshots could become very large and hard to read and maintain.

That is why I think snapshot testing is better suited for unit testing. We only test the component under test and mock the other components in the component tree. This will make our snapshots smaller, more readable and easier to maintain. This of course given that our template in itself is not large.

Mock child components

So mocking is key part to isolate the component under test. Mocking the other components in the component tree can be done either with Jest mocks or with a shallow renderer. If we want to use Jests mock function, we create the whole component tree and then mock our child components with:

jest.mock('./AddTodo')

This will make AddTodo disappear completely from the JSON output of the component tree. If you want to show the AddTodo component tag in the snapshot, we can write:

jest.mock('./AddTodo', () => 'AddTodo')

If we want to use the shallow renderer included in react test renderer:

import ShallowRenderer from 'react-test-renderer/shallow';
...
const renderer = new ShallowRenderer();
const component = renderer.render(<App />);

This shallow renderer doesn’t give access to the component instance to my knowledge. So depending on your use case, either use the shallow renderer or mocking child components for your test.

Snapshots are code

A key part about snapshots is understanding that they are the return value of the template. This return value is code, it should be thought of as code and be reviewed just as much as any other code or test. If we check the Jest documentation for snapshot testing, the first point of best practices is that snapshots are code. So if we don’t review snapshots, then they probably won’t be very helpful either. So make sure to treat your snapshots as code and code review them as such.

Keep components small and snapshots small

Any unit that is large will be hard test and the same applies for templates. We need to keep our templates small to be able to unit testing them in the same way as all other units we test. That a snapshot is large, is not the fault of the snapshot, it is the fault of the template. If we get a big snapshot, then the template is probably hard to understand as well.

Also if we have mocked away all our child components in the component tree and we still have a large snapshot. Then try to think of ways to make the template smaller, by for example refactoring our component to make it easier to test. If we can’t test that our templates work and that they work over time, then they probably will break at some point.

Important to be specific about snapshot testing

Each test should have one clear purpose and one path trough that function. This also applies to snapshot testing, we should have one setup of our component and one snapshot per test. To describe each tests purpose our test description is very important. We need to be very specific about what we want to test, in the same way as we do with other unit tests. We also need to maintain our test description to make sure they are up to date with the test. Given that we have a specific test description together with a proper setup of the component before the snapshot is taken, I believe that we can make each snapshot with a clear purpose and that the developer intent is clearly expressed.

Conclusion

I think snapshot tests are great to test the UI logic of our web applications. With my provided key pieces I think that snapshot testing can give great testing value to our frontend applications. So when snapshot testing, make sure you:

  • Remember that snapshots are code and should be handled with love and care as much as any piece of code
  • Do unit testing and thereby mocking away under components that aren’t under test
  • Keep templates small
  • Keep snapshots small
  • Each snapshot has clear purpose which is properly described in the test description

Thank you for reading my post!!

Please clap or comment!!

--

--

Marcus Stamström

Fullstack software developer with interest of React, Angular, VueJs, Javascript and CSS