One of the features that amazed me about Jest is snapshot testing. It’s not necessarily a Jest-only feature, but more of a technique and concept. Anyways, the first time I’ve seen it was in Jest.
Snapshot testing is the technique of asserting by comparing two different outputs. The way it’s done is quite similar to the visual regression testing of the end to end tests.
For example, if you wanna test that a button hasn’t change its styling, you take a base screenshot of a button, and then every time the test suite is run, a new screenshot is taken and it’s compared pixel by pixel with the base screenshot. If they match, the test passes, but if not there could be a regression, depending if that change is intended or not.
Snapshot testing applies that same technique but based on serializable output, such as text or json, instead of images. Thus, it’s a powerful tool for any kind of output-based feature, where rendering web components can be one of the most important as we’ll see later.
In order to illustrate snapshot testing, let’s see a simple example where we have a function that returns an error message, based on its parameter, and throws an error in case it doesn’t match the conditions:
// error.js
export default function getErrorMessage(code) {
if (code === 1) {
return "The camel walks on a leg";
} else if (code === 2) {
return "Rabbits don't eat carrots";
} else if (code === 3) {
return "Cats don't eat mouses";
}
throw new Error("No error messages for that code");
}
If we attempt to make a test for it using the tools we learnt so far, we’ll create it similar to the following one:
import getErrorMessage from "./error-message";
describe("getErrorMessage", () => {
it("returns camel message when code is 1", () => {
expect(getErrorMessage(1)).toBe("The camel walks on a leg");
});
it("returns rabbit message when code is 2", () => {
expect(getErrorMessage(2)).toBe("Rabbits don't eat carrots");
});
it("returns cat message when code is 3", () => {
expect(getErrorMessage(3)).toBe("Cats don't eat mouses");
});
it("throws an error otherwise", () => {
expect(() => getErrorMessage(4)).toThrow("No error messages for that code");
});
});
The thing is, probably we don’t care much about the exact error message, we just want it to be one and to be consistent. Then, the current test has a couple of issues:
Using snapshot testing we can avoid these issues while providing the same value to the test. Instead of asserting specific error messages, we just want to check that we get some error messages if we use the code 1, 2 or 3, and an error if we don’t, and we want to keep those errors as they are.
Jest introduces specific matchers for snapshot testing: toMatchSnapshot
for serializable values and toThrowErrorMatchingSnapshot
for errors thrown. Let’s use them to rewrite the previous test:
import getErrorMessage from "./error-message";
describe("getErrorMessage", () => {
it("returns an error for a valid code", () => {
expect(getErrorMessage(1)).toMatchSnapshot();
expect(getErrorMessage(2)).toMatchSnapshot();
expect(getErrorMessage(3)).toMatchSnapshot();
});
it("throws an error otherwise", () => {
expect(() => getErrorMessage(4)).toThrowErrorMatchingSnapshot();
});
});
As you can see, the tests became much simpler, specially the ones for the valid code. They’re checking now that the tests give an expected output based on the input by comparing the snapshots.
Of course, the first time we run it, there are no snapshots. Instead, we’ll see they’ve been created, as the following image shows:
Let’s try now to change the value returned when code is equal to 1 in the getErrorMessage
function:
if (code === 1) {
return "The dog walks on a leg";
}
Now you should see the following error, telling you that a snapshot test has failed:
As you can see, it’s giving us the specific error, even pointing to the line of the expect
statement that doesn’t match the snapshot.
At this point, there are two paths:
Let’s say in this case we intentionally made the change. In that case, the failing test is not a regression because it is expected to fail and it needs to be updated.
How? If you run the tests in watch mode, you might have noticed in the previous image the following text:
Inspect your code changes or press
u
to update them.
Just like that, if you press the “u” key, they will be updated. That’s all.
Warning**: don’t fall into the temptation of quickly pressing “u”, which is a common mistake. Make sure the test is really outdated and check carefully your code.
In order to see all the watch mode options, you can press the “w” key:
In addition to the the “u” option to update all failing snapshots, you have the “i” option to update them one by one selectively.
We’ve seen a pretty simple example, but the cool thing of snapshot testing is that it’s easy to use for more complex examples as well. Remember that it works on any serialisable input which means we can use it to compare complex JSON structures, JavaScript objects and even DOM elements.
Our goal is to be the number one source of Vue.js knowledge for all skill levels. We offer the knowledge of our industry leaders through awesome video courses for a ridiculously low price.
More than 200.000 users have already joined us. You are welcome too!
© All rights reserved. Made with ❤️ by BitterBrains, Inc.