Snapshot Testing With Jest

Snapshot Testing With Jest image

Introduction

Since I heard about snapshot testing at the 2017 React Europe conference, I was curious about this new tool for my daily testing.

I must admit that I am a Mocha lover but Jest really caught my attention with this feature.

At some point during testing, everyone has to print the expected result of the test and copy it to your test file. Now, with Jest snapshots, this is not required anymore. Jest will save the expected result for us. Yeah, I know, a dream come true!

A Simple Library Test

Step 1

The first step is to install Jest itself.

// Using NPM
npm install -D jest-cli 

// Using Yarn 
yarn add jest-cli

Be sure to be using a version higher than 14 since that was when snapshots were added to Jest. We can check the version using

➜ npm view jest-cli version
21.2.1

Step 2

I created a simple movie normalizer library.

const movieNormalizer = (movieData) => ({
  title: movieData.Title,
  description: movieData.Plot,
  language: movieData.Language,
  writers: movieData.Writers.split(','),
  actors: movieData.Actors.split(','),
  genres: movieData.Genre.split(','),
});

export default movieNormalizer;

The objective is simple. From this JSON object:

{
  "Title": "Game of Thrones",
  "Year": "2011–",
  "Rated": "TV-MA",
  "Released": "17 Apr 2011",
  "Runtime": "57 min",
  "Genre": "Adventure, Drama, Fantasy",
  "Director": "N/A",
  "Writer": "David Benioff, D.B. Weiss",
  "Actors": "Peter Dinklage, Lena Headey, Emilia Clarke, Kit Harington",
  "Plot": "Nine noble families fight for control over the mythical lands of Westeros, while a forgotten race returns after being dormant for thousands of years.",
  "Language": "English",
  "Country": "USA, UK",
  "Awards": "Won 1 Golden Globe. Another 249 wins & 422 nominations.",
  "Ratings": [
    {
      "Source": "Internet Movie Database",
      "Value": "9.5/10"
    }
  ],
}

we want to get to this normalized object:

{
	"title": "Game of Thrones",
	"description": "Nine noble families fight for control over the mythical lands of Westeros, while a forgotten race returns after being dormant for thousands of years.",
	"language": "English",
	"writers": ["David Benioff", " D.B. Weiss"],
	"actors": ["Peter Dinklage", " Lena Headey", " Emilia Clarke", " Kit Harington"],
	"genres": ["Adventure", " Drama", " Fantasy"]
}

Step 3

Let us add the Jest runner to our NPM script list.

  "scripts": {
    "test": "jest"
  },

Now, it is time to create our test file.

import movieNormalizer from '../src/movieNormalizer';
import movie from './movie.json';

describe('Movie Normalizer', () => {

  test('should return a normalized object from the JSON API schema', () => {
    const normalizedMovie = movieNormalizer(movie);
    expect(normalizedMovie).toMatchSnapshot();
  });
});

The magic happens when we use the toMatchSnapshot() function. This function creates a new snapshot file, if there were no prior snapshots, and will use this one to compare results with any future changes.

Run npm run test.

 PASS  tests/movieNormalizer.test.js
  Movie Normalizer
    ✓ should return a normalized object from the JSON API schema (6ms)

 › 1 snapshot written.
Snapshot Summary1 snapshot written in 1 test suite.

The 1 snapshot written. tells us that now we have a new snapshot file. We can look in the __snapshots__ folder and view our new snapshot file.

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Movie Normalizer should return a normalized object from the JSON API schema 1`] = `
Object {
  "actors": Array [
    "Peter Dinklage",
    " Lena Headey",
    " Emilia Clarke",
    " Kit Harington",
  ],
  "description": "Nine noble families fight for control over the mythical lands of Westeros, while a forgotten race returns after being dormant for thousands of years.",
  "genres": Array [
    "Adventure",
    " Drama",
    " Fantasy",
  ],
  "language": "English",
  "title": "Game of Thrones",
  "writers": Array [
    "David Benioff",
    " D.B. Weiss",
  ],
}
`;

Step 4

What happens when we need to update our snapshots?
I added this change to my normalizer library:

writers: movieData.Writer.split('/'),

I changed the , to / to make the test fail.

FAIL  tests/movieNormalizer.test.js
  Movie Normalizer
    ✕ should return a normalized object from the JSON API schema (9ms)

  ● Movie Normalizer › should return a normalized object from the JSON API schema

    expect(value).toMatchSnapshot()
    
    Received value does not match stored snapshot 1.
    
    - Snapshot
    + Received
    
    @@ -12,9 +12,8 @@
          " Fantasy",
        ],
        "language": "English",
        "title": "Game of Thrones",
        "writers": Array [
    -     "David Benioff",
    -     " D.B. Weiss",
    +     "David Benioff, D.B. Weiss",
        ],
      }

We only need to add the --updateSnapshot parameter and Jest will update our current snapshot.

> jest "--updateSnapshot"

 PASS  tests/movieNormalizer.test.js
  Movie Normalizer
    ✓ should return a normalized object from the JSON API schema (6ms)

 › 1 snapshot updated.
Snapshot Summary1 snapshot updated in 1 test suite.

Testing React Components

Step 1

For React, I created a new simple component.

import React from 'react';

const MovieComponent = ({ movie }) => (
  <div> <h1>My Movie</h1> <ul> <li><b>Title:</b> {movie.title}</li> <li><b>Description:</b> {movie.description}</li> <li><b>Language:</b> {movie.language}</li> <li> <b>Writers:</b> {movie.writers.map((writer) => writer).join(',')} </li> <li> <b>Actors:</b> {movie.actors.map((actor) => actor).join(',')} </li> <li> <b>Genres:</b> {movie.genres.map((genre) => genre).join(',')} </li> </ul> </div>
);

export default MovieComponent;

Step 2

Now let us add our new react test file.

import React from 'react';
import renderer from 'react-test-renderer';

import movie from './movie.json';
import movieNormalizer from '../src/movieNormalizer';
import MovieComponent from '../src/MovieComponent';

describe('Movie Component', () => {

  test('should render the movie information', () => {
    const normalizedMovie = movieNormalizer(movie);
    const component = renderer.create(
      <MovieComponent movie={normalizedMovie} />
    );

    const tree = component.toJSON();
    expect(tree).toMatchSnapshot();
  });
});

It will work exactly as the example before. With any changes we make, the test will fail until we update our new snapshot.

Conclusion

Snapshot testing is a really great feature of Jest. I hope this feature finds its way to others runners like e.g. mocha as well. Maybe we do not require snapshot in all of our scenarios or components, but I know I will start using it more every day.

Repository

Official Documentation

KEEP MOVING FORWARD

William Cabrera / code