Jest has great support for React. Infact, if you're new to React, using Create React App has support for Jest by default. We will continue this tutorial without using Create React App for ease of demonstration.
We've been tasked with building a new React component for a movie list. The component will:
Below is an example of the component we have developed:
// Simple Stateless/Functional React Component
// https://reactjs.org/docs/components-and-props.html
const MovieList = ({ movies }) => {
if (movies.length === 0) {
return <div>There are no movies.</div>;
}
return (
<div>
<div>There are {movies.length} movies</div>
<ul>
{movies.map(function (movie) {
return (
<li key={movie.title}>
{movie.title} - {movie.releaseYear}
</li>
);
})}
</ul>
</div>
);
};
const mockServerResponse = {
movies: [
{
title: "The Hitchhiker's Guide to the Galaxy",
releaseYear: 2005,
},
{
title: "Thor: Ragnarok",
releaseYear: 2017,
},
],
};
render(<MovieList movies={mockServerResponse.movies} />);
We will write tests for the following two scenarios:
We will not test the undefined / null / error data scenarios.
We will want to install the React dependencies as well as the core Jest dependencies:
yarn add --dev react react-dom
yarn add --dev react-test-renderer
yarn add --dev jest babel-jest babel-core babel-preset-env babel-preset-react
Ensure that your top level .babelrc
looks like:
{
"presets": ["env", "react"]
}
Let's create a file for the movie list component:
src/movie-list/index.js
import React from "react";
// Simple Stateless/Functional React Component
// https://reactjs.org/docs/components-and-props.html
const MovieList = ({ movies }) => {
if (movies.length === 0) {
return <div>There are no movies.</div>;
}
return (
<div>
<div>There are {movies.length} movies</div>
<ul>
{movies.map(function (movie) {
return (
<li key={movie.title}>
{movie.title} - {movie.releaseYear}
</li>
);
})}
</ul>
</div>
);
};
export default MovieList;
Additionally create the initial test file:
src/movie-list/__tests__/index.spec.js
import Component from "../";
import React from "react";
import renderer from "react-test-renderer";
describe("movie-list", function () {
describe("when there are no movies", function () {
it("renders the empty message", function () {
const movies = [];
const tree = renderer.create(<Component movies={movies} />);
expect(tree).toMatchSnapshot();
});
});
});
When you first run this test you will see that Jest will create a new snapshot file:
You can also see the additional snapshot file within your project structure:
├── __tests__/
│ ├── __snapshots__/
│ │ └── index.spec.js.snap <--- Newly created snapshot file
│ └── index.spec.js
└── index.js
Let's modify our React component message to have a different message:
src/movie-list/index.js
- return <div>There are no movies.</div>;
+ return <div>Sorry, there are no movies available.</div>;
When we re-run our tests they should fail:
Jest has compared the latest rendering and compared it to a previously recorded snapshot. It has also provided us with a nice human readable diff.
To record a new snapshot you can simply run the test command with the -u
flag which will update snapshots:
Now that the first test has been written, it is up to you to write the second test:
import Component from "../";
import React from "react";
import renderer from "react-test-renderer";
describe("movie-list", function () {
// ...
describe("when there are multiple movies", function () {
it("renders the movies", function () {
const movies = [
{
title: "The Hitchhiker's Guide to the Galaxy",
releaseYear: 2005,
},
{
title: "Thor: Ragnarok",
releaseYear: 2017,
},
];
const tree = renderer.create(<Component movies={movies} />);
expect(tree).toMatchSnapshot();
});
});
});
If you have a more complex React render tree that you wish to mock out, you can make use of jest.mock
to either
change the component's functionality entirely or provide a placeholder React component.
For instance, if you wished to provide a placeholder React component:
jest.mock("third-party-component", function () {
return () => "MockedThirdPartyComponent";
});
When used in conjunction with Jest's snapshotting capabilities, the generated snapshot will be readable:
<div>
<MockedThirdPartyComponent
active={false}
onClick={[MockFunction onClick]}
/>
</div>
In other scenarios you may wish to change the semantics of a third party component entirely. For instance if you wished to change the behaviour of React-Virtualize's autosizer component:
jest.mock("react-virtualized/dist/commonjs/AutoSizer", function () {
return function (props) {
const renderCallback = props.children;
return renderCallback({
width: 600,
});
};
});