# Using ShallowRenderer and Jest snapshots

## Setup

React apps created using `create-react-app` come with Jest installed. To run tests in **watch** mode, use

```text
npm test
```

To run the tests in single-run mode, use .

```text
env CI=true npm test
```

Most CI tools like CircleCI will set the `CI` environment variable for you.

To render components using the [ShallowRenderer](https://reactjs.org/docs/shallow-renderer.html), install `react-test-renderer`:

```text
npm install --save-dev react-test-renderer
```

## Functional Components

Tests for stateless functional components are usually quite straightforward since the HTML rendered by the component is a pure function of the `props` passed into the component. Let's take a Books components as an example:

```javascript
// src/Books.js

import React from "react";

const Books = ({ books }) => {
  if (books.length === 0) {
    return <div>No books found</div>;
  }

  return (
    <ul>
      {books.map(book => {
        return <li key={book._id}>{book.title}</li>;
      })}
    </ul>
  );
};

export default Books;
```

This component displays a list of book titles or a 'no data' message when no books are passed into it. A test for this may look like:

```javascript
// src/Books.test.js

import React from "react";
import ShallowRenderer from "react-test-renderer/shallow";
import Books from "./Books.js";

test('shows a "no data" message when no books are passed in', () => {
  const renderer = new ShallowRenderer();
  renderer.render(<Books books={[]} />);
  const output = renderer.getRenderOutput();

  expect(output).toMatchSnapshot();
});
```

This test uses [Jest's snapshot testing](https://jestjs.io/docs/en/snapshot-testing.html) feature.

The first time this test runs, Jest will store the snapshot in a `__snapshots__` directly relative to the test file. On subsequent runs, Jest compares the stored snapshot with the latest render and reports any differences if found.

Snapshots should be generated by running the tests locally and then committed into your git history. Snapshots will not be generated when tests are run in CI.

## Class components

React components created using classes usually have more going on - namely, `state`, lifecycle methods, and other instance methods. These can be tested using the ShallowRenderer's `getMountedInstance` method to get the instance of the class component.

Here is a class component that fetches some data and stores it in its state, rendering based on whether the data is loaded or not:

```javascript
// src/App.js

import React from "react";

class App extends React.Component {
  state = {
    isLoading: true,
    books: []
  };

  async componentDidMount() {
    const response = await fetch("/api/books");
    const books = await response.json();
    this.setState({
      books,
      isLoading: false
    });
  }

  render() {
    const { isLoading, books } = this.state;
    return (
      <div>
        <h1>Books</h1>

        {isLoading && <div>Loading...</div>}

        {books.length ? (
          <ul>
            {books.map(book => {
              return <li key={book._id}>{book.title}</li>;
            })}
          </ul>
        ) : (
          ""
        )}
      </div>
    );
  }
}

export default App;
```

Tests for this `App` component may look something like the following:

```javascript
// src/App.test.js

import React from "react";
import ShallowRenderer from "react-test-renderer/shallow";
import App from "./App.js";

test("shows a loading message when initialised", () => {
  const renderer = new ShallowRenderer();
  renderer.render(<App />);
  const output = renderer.getRenderOutput();

  expect(output).toMatchSnapshot();
});

test("shows a list of books when data is fetched", async () => {
  fetch.mockResponseOnce(
    JSON.stringify([
      {
        title: "Harry Potter and the Goblet of Fire",
        _id: "123"
      },
      {
        title: "A Song of Ice and Fire",
        _id: "456"
      }
    ])
  );

  const renderer = new ShallowRenderer();
  renderer.render(<App />);
  const app = renderer.getMountedInstance();
  await app.componentDidMount();

  const output = renderer.getRenderOutput();
  expect(output).toMatchSnapshot();
});
```

The tests still assert based on what the component should render in different states, by triggering the state changes using the component instance returned by `getMountedInstance()`.

This test also uses `jest-fetch-mock`.

