Testing Gatsby with Jest

Setting up environment

npm install --save-dev jest babel-jest react-test-renderer babel-preset-gatsby identity-obj-proxy
babel-jest babel-preset-gatsby
  • the last two ensure that the babel preset(s) that are used match what are used internally for your Gatsby site.

Create config file for Jest

  • Gatsby handles its own Babel config
  • Must manually tell Jest to use babel-jest
  • Easiest way to do this is through a 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`],
}
  • transform section tells Jest that js and jsx files need to be transformed using jest-preprocess.js file in project root
const babelOptions = {
presets: ["babel-preset-gatsby"],
}
module.exports = require("babel-jest").createTransformer(babelOptions)
  • moduleNameMapper tells Jest how to handle imports - Main concern here is mocking static file imports because Jest can't handle them. - The mock is a dummy module used instead of the real module inside of tests
  • identity-obj-proxy is used to mock CSS stylesheets - For all other assets we use a manual mock called file-mock.js in a __mocks__ dir at the project root - I'm not sure I understand what role this file actually plays. Seems like it's just exporting something that does nothing. javascript module.exports = "test-file-stub"
  • testPathIgnorePatterns tells Jest to ignore any tests in node_modules or .cache dirs
  • transformIgnorePatterns is required because Gatsby includes untranspiled ES6 code. By default Jest doesn't try to transform code inside node_modules, so we'll get a SyntaxError at runtime. - "This is because gatsby-browser-entry.js isn’t being transpiled before running in Jest. You can fix this by changing the default transformIgnorePatterns to exclude the gatsby module."
  • globals sets __PATH_PREFIX__ which is normally set by Gatsby, and is required by some components - I'm interested in which components require this.
  • testURL must be set if using <Jest 23.5 because some DOM APIs like localStorage don't work well with the default value (about:blank)
  • setupFiles array is a list of files that will be included before all tests are run. - We use loadershim.js to include jest.fn() on a global loader object?
global.___loader = {
enqueue: jest.fn(),
}

Mock Gatsby

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 mocks the graphql function and the Link and StaticQuery components that are exportsrom the gatsby package.

Writing Tests

  • Can either use a __tests__ directory or colocate with the components - I tend to use a separate directory unless the project is very large and each component lives in its own directory.
  • Use either the extension spec.js or .test.js for test files

Example Snapshot Test

import React from "react"
import renderer from "react-test-renderer"
import Header from "../header"
describe("Header", () => {
it("renders correctly", () => {
const tree = renderer
.create(<Header siteTitle="Default Starter" />)
.toJSON()
expect(tree).toMatchSnapshot()
})
})
  • This is a snapshot test using react-test-renderer to render the component
  • On first run, generates a snapshot of the component - Snapshots are generated in a __snapshots__ directory below our __tests__ directory
  • On subsequent runs, compares future snapshots against the original - This allows us to quickly check for regressions and to quickly recognize changes that have occured with our ouput
  • Snapshots are JSON representations of our components - For this reason, snapshots should be checked into VCS
  • To update snapshots, run the test with a -u flag

Running Tests

  • If it does not already exist (it should, by default) create a test script in package.json
"scripts": {
"test": "jest"
}
  • Run npm test --watch to run Jest in watch mode
  • Run npm test -u to update snapshots

Using TypeScript

  • Update the transform in jest.config.js to target ts and tsx files as well - "^.+\\.[jt]sx?$": '<rootDir>/jest-preprocess.js'
  • Update jest.preprocess.js with the @babel/preset-typescript
const babelOptions = {
presets: ["babel-preset-gatsby", "@babel/preset-typescript"],
}

Other Resources

Now What?

If you enjoyed this post, I'd love to continue the conversation on Twitter.

Social media not your thing? Shoot me an email: will@willharris.dev

Designed and developed by Will Harris

Built with

Hosted on

Social

Twitter

GitHub

LinkedIn