Unit Testing Your Gatsby Site with Jest and React Testing library
Published: 2021-01-21 21:15:40

---
title: Unit Testing Your Gatsby Site with Jest and React Testing library

published: true
description: 
tags: 
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/rcfnnnxnvazlccrpil3z.png
canonical_url: https://www.takeshape.io/articles/unit-testing-your-gatsby-site-with-jest-and-react-testing-library/
---

Testing is a crucial piece when it comes to building websites or apps. It gives you more confidence in your product, makes your code better, and helps to avoid unexpected bugs in production.

In this tutorial, we will introduce you to unit testing by showing you how to test your Gatsby site with Jest and the React Testing Library. Let's get started.

## Creating a new Gatsby app

To keep us focused on testing, we’ll use an out of the box Gatsby starter template. To use a Gatsby starter, begin by opening your command-line interface (CLI) and run this command:

```bash
npx gatsby new my-blog-starter https://github.com/gatsbyjs/gatsby-starter-blog
```

*Note -* *If you have* *the* *Gatsby CLI installed on your machine,* *you can* *omit* `*npx*`*.*

Next we will set up the testing environment. Unlike React, Gatsby does not ship with Jest or React Testing Library, so we’ll install those now.

## Setting up our testing environment

Execute the command on the CLI to install the libraries needed for testing a Gatsby site.


## NPM
```
npm install -D jest babel-jest @testing-library/jest-dom @testing-library/react babel-preset-gatsby identity-obj-proxy
```    
## Yarn
```bash
yarn add -D jest babel-jest @testing-library/jest-dom @testing-library/react babel-preset-gatsby identity-obj-proxy
```

With the dependencies installed, we can now create a new folder (`tests`) at the root of the project. The directory structure should look like this:

```bash
    tests
    ├── jest-preprocess.js
    ├── setup-test-env.js
    └── __mocks__
        ├── file-mock.js
        └── gatsby.js
```

With the folder structure in place, next we configure Jest. Let's begin with `jest-preprocess.js`

```javascript
    // tests/jest-preprocess.js
    const babelOptions = {
      presets: ["babel-preset-gatsby"],
    }
    module.exports = require("babel-jest").createTransformer(babelOptions)
```

This config tells Gatsby how to compile our tests with Babel because both Gatsby and Jest use modern JavaScript and JSX.

```javascript
    // tests/setup-test-env.js
    import "@testing-library/jest-dom/extend-expect"
```

As you can see, this file allows us to import `jest-dom` in one place and then use it on every test file.

```javascript
    // tests/__mocks__/file-mock.js
    module.exports = "test-file-stub"
```

If you need to mock static assets, then this file is required to do so. We won't have that use-case, but be aware of what it does.

```javascript
    // tests/__mocks__/gatsby.js
    const React = require("react")
    const gatsby = jest.requireActual("gatsby")
    module.exports = {
      ...gatsby,
      graphql: jest.fn(),
      Link: jest.fn().mockImplementation(
        // these props are invalid for an `a` tag
        ({
          activeClassName,
          activeStyle,
          getProps,
          innerRef,
          partiallyActive,
          ref,
          replace,
          to,
          ...rest
        }) =>
          React.createElement("a", {
            ...rest,
            href: to,
          })
      ),
      StaticQuery: jest.fn(),
      useStaticQuery: jest.fn(),
    }
```

This file allows us to mock some Gatsby features in order to query data with GraphQL or use the `Link` component. Make sure to name the folder `__mocks__` and the file `gatsby.js` — otherwise, Gatsby will throw errors.

With this configuration in place, we can dive into Jest and customize it to follow our needs. Let's begin by creating a `jest.config.js` file in the root of the project and then add this code below.

```javascript
    // jest.config.js
    module.exports = {
      transform: {
        "^.+\\.jsx?$": `<rootDir>/tests/jest-preprocess.js`,
      },
      moduleNameMapper: {
        ".+\\.(css|styl|less|sass|scss)$": `identity-obj-proxy`,
        ".+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": `<rootDir>/tests/__mocks__/file-mock.js`,
      },
      testPathIgnorePatterns: [`node_modules`, `\\.cache`, `<rootDir>.*/public`],
      transformIgnorePatterns: [`node_modules/(?!(gatsby)/)`],
      globals: {
        __PATH_PREFIX__: ``,
      },
      setupFilesAfterEnv: ["<rootDir>/tests/setup-test-env.js"],
    }
```

This file can look confusing at first, but it's relatively easy to grasp. Let's break it down:


1. `transform` use Babel to compile the JSX code.
2. `moduleNameMapper` mock the static assets.
3. `testPathIgnorePatterns` exclude the folders listed in the array when running the tests.
4. `transformIgnorePatterns` exclude the folders listed in the array when transforming the JSX code.
5. `globals` indicate to Jest the folders to test.
6. `setupFilesAfterEnv` import `jest-dom` before test runs.

The last step of the config consists of tweaking the `package.json` file to run Jest with the CLI.

```json
    // package.json
      "scripts": {
        "test": "jest"
      }
```

Phew! The setting up is complete. Let's now start writing our tests in the next section.

## Writing the unit tests

Unit testing is a method that ensures that a section of an application behaves as intended.
In this article, we will be testing the `SEO` component and the Home page (`index.js`). Let's structure the folder as follows:

```bash
    src
    ├── components
    | ├── seo.js
    | ├── bio.js
    | ├── layout.js
    | └── __tests__
    | |    |   └── seo.js
    ├── pages
    | ├── 404.js
    | ├── index.js
    | └── __tests__
    | |    |   └── index.js
```

You can use `.spec` or `.test` to create a testing file or put the files in the `__tests__` folder. Jest will only execute the files under the `__tests__` folder.

```javascript
    // components/seo.js
    import React from "react"
    import PropTypes from "prop-types"
    import { Helmet } from "react-helmet"
    import { useStaticQuery, graphql } from "gatsby"
    
    const SEO = ({ description, lang, meta, title }) => {
      const { site } = useStaticQuery(
        graphql`
          query {
            site {
              siteMetadata {
                title
                description
                social {
                  twitter
                }
              }
            }
          }
        `
      )
    
      const metaDescription = description || site.siteMetadata.description
      const defaultTitle = site.siteMetadata?.title
    
      return (
        <Helmet
          htmlAttributes={{
            lang,
          }}
          title={title}
          titleTemplate={defaultTitle ? `%s | ${defaultTitle}` : null}
          meta={[
            {
              name: `description`,
              content: metaDescription,
            },
            {
              property: `og:title`,
              content: title,
            },
            {
              property: `og:description`,
              content: metaDescription,
            },
            {
              property: `og:type`,
              content: `website`,
            },
            {
              name: `twitter:card`,
              content: `summary`,
            },
            {
              name: `twitter:creator`,
              content: site.siteMetadata?.social?.twitter || ``,
            },
            {
              name: `twitter:title`,
              content: title,
            },
            {
              name: `twitter:description`,
              content: metaDescription,
            },
          ].concat(meta)}
        />
      )
    }
    
    SEO.defaultProps = {
      lang: `en`,
      meta: [],
      description: ``,
    }
    
    SEO.propTypes = {
      description: PropTypes.string,
      lang: PropTypes.string,
      meta: PropTypes.arrayOf(PropTypes.object),
      title: PropTypes.string.isRequired,
    }
    
    export default SEO
```    
    

Now, let's write the unit tests for the `SEO` component.

```javascript
    // components/__tests__/seo.js
    import React from "react"
    import { render } from "@testing-library/react"
    import { useStaticQuery } from "gatsby"
    
    import Helmet from "react-helmet"
    import SEO from "../seo"
    
    describe("SEO component", () => {
      beforeAll(() => {
        useStaticQuery.mockReturnValue({
          site: {
            siteMetadata: {
              title: `Gatsby Starter Blog`,
              description: `A starter blog demonstrating what Gatsby can do.`,
              social: {
                twitter: `kylemathews`,
              },
            },
          },
        })
      })
    
      it("renders the tests correctly", () => {
        const mockTitle = "All posts | Gatsby Starter Blog"
        const mockDescription = "A starter blog demonstrating what Gatsby can do."
        const mockTwitterHandler = "kylemathews"
    
        render(<SEO title="All posts" />)
        const { title, metaTags } = Helmet.peek()
    
        expect(title).toBe(mockTitle)
        expect(metaTags[0].content).toBe(mockDescription)
        expect(metaTags[5].content).toBe(mockTwitterHandler)
        expect(metaTags.length).toBe(8)
      })
    })
```

We start by importing React Testing Library, which allows rendering the component and giving access to DOM elements. After that, we mock the GraphQL query with `useStaticQuery` to provide the data to the `SEO` component.

Next, we rely on the `render` method to render the component and pass in the `title` as props. With this, we can use `Helmet.peek()` to pull the metadata from the mocked GraphQL query. 

Finally, we have four test cases:


1. It tests if the `title` from the metadata is equal to `All posts | Gatsby Starter Blog`.
2. It checks if the `description` from the metadata is equal to `A starter blog demonstrating what Gatsby can do.`.
3. It tests if the `twitter` from the metadata is equal to `kylemathews`.
4. It checks if the length of the `metaTags` array is equal to 8.

To run the tests, we have to execute this command on the CLI.


    npm test

Or, for `yarn`


    yarn test

All tests should pass as expected by showing some nice green sticks on the CLI.

```javascript
    // pages/index.js
    
    import React from "react"
    import { Link, graphql } from "gatsby"
    
    import Bio from "../components/bio"
    import Layout from "../components/layout"
    import SEO from "../components/seo"
    
    const BlogIndex = ({ data, location }) => {
      const siteTitle = data.site.siteMetadata?.title || `Title`
      const posts = data.allMarkdownRemark.nodes
      if (posts.length === 0) {
        return (
          <Layout location={location} title={siteTitle}>
            <SEO title="All posts" />
            <Bio />
            <p>
              No blog posts found. Add markdown posts to "content/blog" (or the
              directory you specified for the "gatsby-source-filesystem" plugin in
              gatsby-config.js).
            </p>
          </Layout>
        )
      }
    
      return (
        <Layout location={location} title={siteTitle}>
          <SEO title="All posts" />
          <Bio />
          <ol style={{ listStyle: `none` }}>
            {posts.map(post => {
              const title = post.frontmatter.title || post.fields.slug
              return (
                <li key={post.fields.slug}>
                  <article
                    className="post-list-item"
                    itemScope
                    itemType="http://schema.org/Article"
                  >
                    <header>
                      <h2>
                        <Link
                          data-testid={post.fields.slug + "-link"}
                          to={post.fields.slug}
                          itemProp="url"
                        >
                          <span itemProp="headline">{title}</span>
                        </Link>
                      </h2>
                      <small>{post.frontmatter.date}</small>
                    </header>
                    <section>
                      <p
                        data-testid={post.fields.slug + "-desc"}
                        dangerouslySetInnerHTML={{
                          __html: post.frontmatter.description || post.excerpt,
                        }}
                        itemProp="description"
                      />
                    </section>
                  </article>
                </li>
              )
            })}
          </ol>
        </Layout>
      )
    }
    
    export default BlogIndex
    export const pageQuery = graphql`
      query {
        site {
          siteMetadata {
            title
          }
        }
        allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) {
          nodes {
            excerpt
            fields {
              slug
            }
            frontmatter {
              date(formatString: "MMMM DD, YYYY")
              title
              description
            }
          }
        }
      }
    `
```

Notice that here, we use `data-testid` on some elements to be able to select them from the testing file. Let's write the unit tests for the home page.

```javascript
    //pages/__tests__/index.js
    import React from "react"
    import { render } from "@testing-library/react"
    import { useStaticQuery } from "gatsby"
    
    import BlogIndex from "../index"
    
    describe("BlogIndex component", () => {
      beforeEach(() => {
        useStaticQuery.mockReturnValue({
          site: {
            siteMetadata: {
              title: `Gatsby Starter Blog`,
              description: `A starter blog demonstrating what Gatsby can do.`,
              social: {
                twitter: `kylemathews`,
              },
            },
          },
        })
      })
      it("renders the tests correctly", async () => {
        const mockData = {
          site: {
            siteMetadata: {
              author: "John Doe",
            },
          },
          allMarkdownRemark: {
            nodes: [
              {
                excerpt: "This is my first excerpt",
                fields: {
                  slug: "first-slug",
                },
                frontmatter: {
                  date: "Nov 11, 2020",
                  title: "My awesome first blog post",
                  description: "My awesome first blog description",
                },
              },
              {
                excerpt: "This is my second excerpt",
                fields: {
                  slug: "second-slug",
                },
                frontmatter: {
                  date: "Nov 12, 2020",
                  title: "My awesome second blog post",
                  description: "My awesome second blog description",
                },
              },
            ],
          },
        }
    
        const { getByTestId } = render(
          <BlogIndex data={mockData} location={window.location} />
        )
    
        const { nodes } = mockData.allMarkdownRemark
        const post1 = "first-slug-link"
        const post2 = "second-slug-desc"
    
        expect(getByTestId(post1)).toHaveTextContent(nodes[0].frontmatter.title)
        expect(getByTestId(post2)).toHaveTextContent(
          nodes[1].frontmatter.description
        )
        expect(nodes.length).toEqual(2)
      })
    })
```

As you can see, we start by mocking the GraphQL query. Next, we create the dummy data and then pass in the object to the `BlogIndex` component.

After that, we pull out `getTestId` from the `render` method, which enables us to select elements from the DOM. With this in place, we can now explain what the tests do:


1. It tests if the title of the `Link` component of the first article is equal to `My awesome first blog post`
2. It test if the description of the second article is equal to `My awesome second blog description`.
3. It tests if the array of articles is equal to 2.

Now, execute this command on the CLI.


    npm test

Or, for `yarn`


    yarn test

All unit tests should pass.
With this final step, we are now able to test our Gatsby site with Jest and React Testing Library. You can find the finished project in this [Github repo](https://github.com/ibrahima92/gatsby-unit-testing).

## Conclusion

In this tutorial, we learned how to test a Gatsby site with Jest and React Testing Library. Testing is often seen as a tedious process, but the more you dig into it, the more value you get on your app.