Setting Up Gatsby for Unit Testing

We are going to be using the Jest testing framework for react which was developed by Facebook. So first, we install all the Jest dependencies that play well with Gatsby.

I’m not going to go into detail of how all these configuration files function since the Gatsby Unit Testing Config documentation already walks through all the files in detail.

Install Dependencies

# power down the app and cd into frontend on your local computer
npm install --save-dev jest@26.6.3 babel-jest@26.6.3 react-test-renderer babel-preset-gatsby identity-obj-proxy

At the time of this writing babel-jest version 27.0.0 breaks with gatsby. As a work-around we installed babel-jest version 26.6.3 along with jest version 26.6.3.

We need to create five configuration files for Jest:

  1. jest.config.js
  2. jest-preprocess.js
  3. __mocks__/file-mock.js
  4. loadershim.js
  5. __mocks__/gatsby.js

Create Config File for Jest

// frontend/jest.config.js

module.exports = {
  transform: {
    "^.+\\.jsx?$": `<rootDir>/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>/__mocks__/file-mock.js`,
  },
  testPathIgnorePatterns: [`node_modules`, `\\.cache`, `<rootDir>.*/public`],
  transformIgnorePatterns: [`node_modules/(?!(gatsby)/)`],
  globals: {
    __PATH_PREFIX__: ``,
  },
  testURL: `http://localhost`,
  setupFiles: [`<rootDir>/loadershim.js`],
}

Create jest-preprocess.js

const babelOptions = {
  presets: ["babel-preset-gatsby"],
}

module.exports = require("babel-jest").createTransformer(babelOptions)

Create __mocks__/file-mock.js (note double underscores)

// frontend/__mocks__/file-mock.js

module.exports = "test-file-stub"

Create Global loadershim.js

// frontend/loadershim.js

global.___loader = {
  enqueue: jest.fn(),
}

Mock Gatsby

# frontend/__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(),
}

Finally we need to update our package.json file where the default test script has not been set to jest.

// frontend/package.json

"scripts": {
    "build": "gatsby build",
    "develop": "gatsby develop",
    "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\"",
    "start": "npm run develop",
    "serve": "gatsby serve",
    "clean": "gatsby clean",
    "test": "jest"

Creating a Header Component

Before we begin writing tests lets build a header component to display the title of our site. The component below is based is pulled from the Gatsby.js documentation for testing components with GraphQL where they have a detailed breakdown of all the elements in this header that will enable us to test with Jest.

Start by creating a new directory and header.js file:

mkdir frontend/src/components
touch frontend/src/components/header.js

… and populate the header.js file with the following:

// frontend/src/components/header.js

// 1)
import { Link } from "gatsby"

// 2)
import PropTypes from "prop-types"

// 3)
import React from "react"

// 4)
import { useStaticQuery, graphql } from "gatsby"

// 5)
export const PureHeader = ({ data }) => (

  // 6)
  <header
    style={{
      background: `rebeccapurple`,
      marginBottom: `1.45rem`,
    }}
  >
    <div
      style={{
        margin: `0 auto`,
        maxWidth: 960,
        padding: `1.45rem 1.0875rem`,
      }}
    >
      <h1 style={{ margin: 0 }}>

        // 7)
        <Link
          to="/"
          style={{
            color: `white`,
            textDecoration: `none`,
          }}
        >

          // 8)
          {data.site.siteMetadata.title}
        </Link>
      </h1>
    </div>
  </header>
)

// 9)
export const Header = props => {

  // 10)
  const data = useStaticQuery(graphql`
    query {
      site {
        siteMetadata {
          title
        }
      }
    }
  `)
  // 11)
  return <PureHeader {...props} data={data}></PureHeader>
}

// 12)
Header.propTypes = {
  siteTitle: PropTypes.string,
}

// 13)
Header.defaultProps = {
  siteTitle: ``,
}

// 14
export default Header

Structure of a Gatsby Component

Lets break down what we are building in this header component step by step.

// 1)
import { Link } from "gatsby"

We start by importing the Gatsby Link component which allows us to link between internal pages in Gatsby. When we want to navigate between internal Gatsby pages we will by using the Link tag instead of traditional anchor (a) tags.

// 2) 
import PropTypes from "prop-types"

We will be using propTypes which help us to keep track if our component is receiving the correct type data or not.

// 3)
import React from "react"

Of course we import react since Gatsby is built on top of React.

// 4)
import { useStaticQuery, graphql } from "gatsby"

We will be using useStaticQuery, (a React hook), and graphql to query the siteMetadata for the title we’ve set for our site in gatsby-config.js.

// 5)
export const PureHeader = ({ data }) => (

Its this function PureHeader we will be testing with Jest. More on this later.

// 6)
<header
    style={{
      background: `rebeccapurple`,
      marginBottom: `1.45rem`,
    }}
  >

We define our HTML header element with some inline styling.

// 7)
        <Link
          to="/"
          style={{
            color: `white`,
            textDecoration: `none`,
          }}
        >

Using the Gatsby Link component we link the header to the index page of the site.

          // 8)
          {data.site.siteMetadata.title}

Using the data prop we passed into our function, we pull the title of our site from Gatsby-config.js. Note the curly braces. Anytime we want to embed a javascript snippet or in this case our {data.site.siteMetadata.title} variable, we wrap the javascript code in braces.

// 9)
export const Header = props => {

This is the second function where we will use useStaticQuery and graphql to query the siteMetadata in the Gatsby-config.js file.

  // 10)
  const data = useStaticQuery(graphql`
    query {
      site {
        siteMetadata {
          title
        }
      }
    }
  `)

Here we define the query with graphql and execute the query with useStaticQuery.

// 11)
  return <PureHeader {...props} data={data}></PureHeader>

We return the PureHeader with the data prop containing the siteMetadata.

// 12)
Header.propTypes = {
  siteTitle: PropTypes.string,
}

Now we can implement propTypes for our component which in this case is siteTitle of the PropType string. This isn’t absolutely necessary for the Gatsby component to render properly but it is a good coding practice to explicitly enforce type casting in our app.

// 13)
Header.defaultProps = {
  siteTitle: ``,
}

Here we define the defaultProps which in this case is an empty string but, if we were to import this component into a page and not pass in the siteTitle argument we could define a default siteTitle.

// 14)
export default Header

Finally we export our Header function to make available for import into a page or another component.

What is a Pure Function and Why Use It?

We have two functions in this header.js file:

  1. export const PureHeader = ({ data }) => ( // … code
  2. export const Header = props => { // … code

Of the two the first is a pure function. What is a pure function?

A pure function is a deterministic function. This means when a same input is passed every time, the function will return same output.

A pure function will have the following properties:

  • It depends only on its own arguments
  • It wont try to change variables out of its scope
  • It doesn’t produce any side effects

Its the first bullet point that concerns us. Our PureHeader only deals with the argument, ({data}), that was passed by the Header function.

The second Header function is not pure because its changing variables out of its scope when it runs useStaticQuery and graphql to query siteMetadata.

Why does this matter?

If we were to write a jest test and run it against the second Header function the test would fail because Jest needs to mock the query itself before running the test. By testing the PureHeader we can use Jest to mock the query and pass in our mocked data into the imported PureHeader.

What an elegant solution to the problem of testing GraphQL components!

Before we write our Jest header.js test lets import the Header into our index.js file.

Import Header Component into Index File

Lets refactor our index page to import our Header function by adding:

// frontend/src/pages/index.js

// code ...

import Header from "../components/header"

// code ...

<Header />

The complete updated index.js file is follows:

// frontend/src/pages/index.js

import React from "react"
import { useQuery } from '@apollo/client';
import gql from 'graphql-tag';
import Header from "../components/header"

const APOLLO_QUERY = gql`
  query {
    todo(id: 1) {
      id
      title
      task
    }
  }
`;

export default function Home() {
  const { loading, error, data } = useQuery(APOLLO_QUERY)
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error!</p>;
  
  return(
    <div>
      <Header />
      <div>
        Hello world!
      </div>

      <div>
        Todo Title: {data.todo.title}
      </div>
    </div>
    
  )
}

Configure Site Meta Data for Gatsby

We now can define global meta data properties in gatsby-config .js file where we will import our site’s into our index file.

// frontend/gatsby-config.js

/**
 * Configure your Gatsby site with this file.
 *
 * See: https://www.gatsbyjs.com/docs/gatsby-config/
 */

module.exports = {
  siteMetadata: {
    title: `Gatsby Todo App`,
    description: `A fabulous description of our Todo app here...`,
    author: `Ron Leeson`,
  },
  plugins: [],
}

Writing Our First Test

The first test we write will be for the Header component by creating a directory in our components directory for __tests__ and a file for the header.js test.

mkdir frontend/src/components/__tests__
touch frontend/src/components/__tests__/header.js

Populate the header.js file with the following:

// frontend/src/components/__tests__/header.js

import React from 'react'

// 1)
/**
Render the React component to a pure JavaScript objec without depending on the DOM
**/
import renderer from 'react-test-renderer'

// 2) Import our PureHeader we just built
import { PureHeader as Header } from '../header'

// 3) 
/**
Using the Jest describe method for containing our test
**/
describe("Header", () => {
  it("renders correctly", () => {
    // Created using query from Header.js
    const data = {
      site: {
        siteMetadata: {
          title: "Gatsby Todo App"
        },
      },
    }

    // 4) Make a snapshot of the PureHeader
    const tree = renderer.create(<Header data={data} />).toJSON()

    // 5) 
    /**
    On the initial test we take the snapshot, on subsequent tests 
    we compare the new snapshot against the initial snapshot for
    a match. If they match test passes, if they don't match 
    the test fails.
    **/
    expect(tree).toMatchSnapshot()
  })
})

With all dependencies installed, our new Header component imported into our refactored index file, we are now ready to rebuild the frontend docker container.

Rebuild Frontend Docker Container

docker-compose build frontend

If you run into some errors while building or booting up the app you can tell Docker to clear the cache before building the frontend container:

docker-compose build --no-cache frontend

Now we are ready to boot up and see if our index page renders:

docker-compose -f docker-compose.yml -f docker-compose-dev.yml up

# Or using the Makefile command
make dev

Going to http://localhost:8000 in your browser you should see something like this:

If you get an error because the Django backend server failed to boot, try starting the backend as follows:

docker-compose up -d database
docker-compose up -d server

# now start the frontend in development mode
make dev

Running Jest Tests

To run tests in Jest here are a few of the available commands:

  • npm test // Run all the tests once
  • npm test — –watch // Tell Jest to watch for changes in code and run tests again
  • npm test — –u // Update snapshots

Since we are running Jest in our docker frontend container the command to run a test looks like:

# this will take the intial snapshot of the Header component
docker-compose run frontend npm test

# run the test again and changes to the Header will be compared against the inital snapshot
docker-compose run frontend npm test

Running the commands above you should see like the following in your terminal:

Jest Testing Apollo/Client Queries

Its considered best practice to test the smallest component possible rather than test a full page. So, instead of testing the full index.js page, we can move the GET_TODO_QUERY out of index.js into a separate todo.js component.

Start by creating a file for todo.js:

// frontend/src/components/todo.js

import React from "react"
import { gql, useQuery } from '@apollo/client'
import PropTypes from "prop-types"

export const GET_TODO_QUERY = gql`
  query GetTodo($id: Int) {
    todo(id: $id) {
      id
      title
      task
    }
  }
`;

export const Todo = ({ id }) => {
  const { loading, error, data } = useQuery(
    GET_TODO_QUERY,
    { variables: { id }}
  );
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error!</p>;

  return (
    <div>
      <h5>Todo:</h5>
      id: {data.todo.id} <br />
      title: {data.todo.title}
    </div>
  )
}

Todo.propTypes = {
  id: PropTypes.number.isRequired
}

Todo.defaultProps = {
  id: ``,
}

export default Todo

Refactor the index.js file to import our new Todo component:

// frontend/src/pages/index.js

import React from "react"
import Header from "../components/header"
import Todo from "../components/todo"

export default function Home() {
  return(
    <div>

      <Header />

      <div>
        Hello world!
      </div>

      <Todo id = {2} />
      
    </div>
    
  )
}

Testing Todo Component with GraphQL

import React from "react"
import { MockedProvider } from '@apollo/client/testing';
import renderer from "react-test-renderer"
// The component AND the query need to be exported
import { GET_TODO_QUERY, Todo } from '../todo';

const mocks = [
  {
    request: {
      query: GET_TODO_QUERY,
      variables: {
        id: 1,
      },
    },
    result: {
      data: {
        todo: { id: '1', title: 'Test' },
      },
    },
  },
];

it('renders without error', () => {
  renderer.create(
    <MockedProvider mocks={mocks} addTypename={false}>
      <Todo id={1} />
    </MockedProvider>,
  );
});

Run the Jest Test Suite

docker-compose run frontend npm test

# or if you want Jest to run in the background watching for changes
docker-compose run frontend npm test -- --watchAll

# if you want to update your snapshots
docker-compose run frontend npm test -- --u

And there we have it, testing for Gatsby page queries and Apollo graphql queries.

Having to type out these long docker-compose commands to run tests can get old so, lets update our Makefile with some shortcuts for our Gatsby tests and while we are at it, lets add shortcuts for our Django backend tests as well. Remember those? I know, it was a long time ago…

build:
	docker-compose build

dev:
	docker-compose -f docker-compose.yml -f docker-compose-dev.yml up 

dev-down:
	docker-compose -f docker-compose.yml -f docker-compose-dev.yml down

dev-build-no--cache:
	docker-compose -f docker-compose.yml -f docker-compose-dev.yml build --no-cache frontend

prod:
	docker-compose up -d 

prod-down:
	docker-compose down


# Django Backend Testing
dj-test:
	docker-compose run server ./manage.py test

pytest:
	docker-compose run server pytest

pytest-verbose:
	docker-compose run server pytest -vv

# Gatsby Frontend Testing
test:
	docker-compose run frontend npm test

watch:
	docker-compose run frontend npm test -- --watchAll

update-snapshot:
	docker-compose run frontend npm test -- --u


up-non-daemon:
	docker-compose up

start:
	docker-compose start

stop:
	docker-compose stop

down:
	docker-compose down

restart:
	docker-compose stop && docker-compose start

restart-dev:
	docker-compose down && docker-compose -f docker-compose.yml -f docker-compose-dev.yml up -d

restart-frontend:
	docker-compose stop frontend && docker-compose start frontend

restart-server:
	docker-compose stop server && docker-compose start server

shell-server:
	docker exec -ti server bash

shell-frontend:
	docker exec -ti frontend bash

shell-db:
	docker exec -ti postgres bash

log-server:
	docker-compose logs server

log-frontend:
	docker-compose logs frontend

log-db:
	docker-compose logs postgres

collectstatic:
	docker exec server /bin/sh -c "python manage.py collectstatic --noinput"

migrations:
	docker exec server /bin/sh -c "python manage.py makemigrations; python manage.py migrate"

Lets run all our tests to date for both front and back ends:

# Run Gatsby tests
make test

# Run Django pytests
make pytest

And I am pleased as punch to see all my tests pass. Woohoo! We haven’t broken anything. If we had some fails we would know where to go to fix them. Got to love automated testing. 🙂

Conclusion

Now that we have…

  • Our Gatsby/Docker development environment setup
  • Gatsby configured to use the Apollo/Client
  • Gatsby configured for using Jest to test both page and Apollo queries

… we have all of the basic infrastructure we need to build out our Gatsby Todo app frontend.

Next up in chapter 11 we will restructure our Gatsby project to implement a layout component which allows us to share markup, styles, and functionality across multiple pages.