How to Jest Snapshot Test the Difference

 by Robin Wieruch
 - Edit this Post
jest snapshot test difference

Snapshot tests are a common way to write lightweight component tests. When a snapshot test runs for the first time, it stores its output (e.g. rendered component's HTML structure) in a snapshot output file. Every time the snapshot test runs again, another snapshot output file gets created; which is used to diff the output against the old snapshot test's output file. If the snapshot's output has changed, the developer accepts or denies the changes. This way, developers keep an overview of their recent changes.

import React from 'react';
const App = () => {
const [counter, setCounter] = React.useState(0);
return (
<div>
<h1>My Counter</h1>
<Counter counter={counter} />
<button type="button" onClick={() => setCounter(counter + 1)}>
Increment
</button>
<button type="button" onClick={() => setCounter(counter - 1)}>
Decrement
</button>
</div>
);
};
export const Counter = ({ counter }) => (
<div>
<p>{counter}</p>
</div>
);
export default App;

The code snippet shows a React application that implements a counter which can be increased/decreased with a React Hook by using one of two rendered buttons. A straightforward snapshot test for the React component could be implemented the following way:

import React from 'react';
import renderer from 'react-test-renderer';
import App from './App';
describe('App', () => {
it('renders', () => {
const component = renderer.create(<App />);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});

If one would run the snapshot test, the following snapshot output file would be generated:

exports[`App increments the counter 1`] = `
<div>
<h1>
My Counter
</h1>
<div>
<p>
0
</p>
</div>
<button
onClick={[Function]}
type="button"
>
Increment
</button>
<button
onClick={[Function]}
type="button"
>
Decrement
</button>
</div>
`;

That's the most basic approach for snapshot testing in React. The question for this tutorial: What happens if you want to snapshot test a provoked change of your re-rendered component?

For instance, in the case of our React application, one could invoke one of the two buttons to cause a state change which increases the counter which would lead to a re-render of the component. Afterward, a new snapshot test could be used to assert the differences of the rendered output:

import React from 'react';
import renderer from 'react-test-renderer';
import App from './App';
describe('App', () => {
it('increments the counter', () => {
const component = renderer.create(<App />);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
component.root.findAllByType('button')[0].props.onClick();
tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});

After running the snapshot test, we would end up with two snapshot outputs in the same snapshot output file. The following code snippet shows only the second output for the changed/re-rendered component:

exports[`App increments the counter 2`] = `
<div>
<h1>
My Counter
</h1>
<div>
<p>
1
</p>
</div>
<button
onClick={[Function]}
type="button"
>
Increment
</button>
<button
onClick={[Function]}
type="button"
>
Decrement
</button>
</div>
`;

Again, that's the most basic approach for testing a changed/re-rendered component. However, there are two drawbacks for this minimal approach which can be seen in the previous snapshot's output:

  • 1) The entire component gets snapshotted again. (Redundancy)
  • 2) It's not clear that the snapshot was performed to assert a change regarding a re-rendered component. Rather it's just a straightforward snapshot again. (Missing Context)

Let's implement a better version for snapshot tests to assert differences that can happen after re-renderings caused by user interaction or other side-effects. First, install this neat helper library for asserting a snapshot difference:

npm install --save-dev snapshot-diff

Second, setup the helper library by extending your Jest expect method with a new functionality:

import React from 'react';
import renderer from 'react-test-renderer';
import { toMatchDiffSnapshot } from 'snapshot-diff';
expect.extend({ toMatchDiffSnapshot });
import App from './App';
describe('App', () => {
it('increments the counter', () => {
...
});
});

And third, make use of the new functionality to create a snapshot for the difference between two component renders:

import React from 'react';
import renderer from 'react-test-renderer';
import { toMatchDiffSnapshot } from 'snapshot-diff';
expect.extend({ toMatchDiffSnapshot });
import App from './App';
describe('App', () => {
it('increments the counter', () => {
const component = renderer.create(<App />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
component.root.findAllByType('button')[0].props.onClick();
const treeUpdate = component.toJSON();
expect(tree).toMatchDiffSnapshot(treeUpdate);
});
});

Now, you get the second output for the re-rendered component in your snapshot output file:

exports[`App increments the counter 2`] = `
"Snapshot Diff:
- First value
+ Second value
@@ -2,11 +2,11 @@
<h1>
My Counter
</h1>
<div>
<p>
- 0
+ 1
</p>
</div>
<button
onClick={[Function onClick]}
type=\\"button\\""
`;

If you compare this snapshot's output to the previous one, you can see that we got rid of the two mentioned drawbacks. First, we don't render the whole component again, but only the part that has changes in addition to its surrounding environment. Second, the snapshot test's output doesn't look like a rendered component anymore, but like a diff between two outputs shown with the + and - prefixes. Only by looking at the snapshot's output file, a developer can tell that 1) the snapshot test was caused by a change of the component and 2) that the rendered output has changed from X to Y.

Keep reading about 

If you are using Snapshot Tests with Jest for your components, there are a few pitfalls you have to be aware of. Two of them are very likely to apply to your written tests as well: 1) The output of…

Writing tests is an essential part of software development to ensure a robust application. Tests enable us to automatically verify that our application is working on a certain level. The certain level…

If you found this blog post helpful, please consider supporting what I do.

The Road to React

Learn React by building real world applications. No setup configuration. No tooling. Plain React in 200+ pages of learning material. Learn React like 50.000+ readers.

Get it on Amazon.