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:
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
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:
    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
    // 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.
    // 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.
    // 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.
    // 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.
    // 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:
- 
transformuse Babel to compile the JSX code. - 
moduleNameMappermock the static assets. - 
testPathIgnorePatternsexclude the folders listed in the array when running the tests. - 
transformIgnorePatternsexclude the folders listed in the array when transforming the JSX code. - 
globalsindicate to Jest the folders to test. - 
setupFilesAfterEnvimportjest-dombefore test runs. 
The last step of the config consists of tweaking the package.json file to run Jest with the CLI.
    // 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:
    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.
    // 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.
    // 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:
- It tests if the 
titlefrom the metadata is equal toAll posts | Gatsby Starter Blog. - It checks if the 
descriptionfrom the metadata is equal toA starter blog demonstrating what Gatsby can do.. - It tests if the 
twitterfrom the metadata is equal tokylemathews. - It checks if the length of the 
metaTagsarray 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.
    // 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.
    //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:
- It tests if the title of the 
Linkcomponent of the first article is equal toMy awesome first blog post - It test if the description of the second article is equal to 
My awesome second blog description. - 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.
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.
              
    
Top comments (1)
Good Post!