Configuring Jest

Let’s walk through a typical Jest test suite built for a Gatsby site. This will demonstrate a standard configuration of Jest that integrates seamlessly with Gatsby’s build process. To begin, we’ll spin up a new version of the Gatsby blog starter:

$ gatsby new gtdg-ch11-unit-testing gatsbyjs/gatsby-starter-blog
$ cd gtdg-ch11-unit-testing

Jest requires us to install several dependencies. We need to install babel-jest and babel-preset-gatsby in order to ensure that the Babel presets Jest will use match those that are found in our Gatsby site, since Gatsby uses its own unique Babel configuration (this is particularly important if you have an unusual Babel preset that was customized beyond Gatsby’s own preset and needs to be accounted for in your unit testing):

$ npm install --save-dev jest babel-jest react-test-renderer \
  babel-preset-gatsby identity-obj-proxy

In the same directory as our gatsby-config.js file (i.e., in the root of the Gatsby project), we’ll add a jest.config.js file that contains some defaults and also instructs Jest to use the babel-jest package:

// 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: ``, 
  setupFiles: [`<rootDir>/loadershim.js`], 
}

The transform object instructs Jest to transform all files with the .js or .jsx extension using a jest-preprocess.js file, which Jest expects to encounter in the project root. Let’s go ahead and add this file now to tell Jest that it should use Gatsby’s Babel preset when setting up this transformer:

// jest-preprocess.js
const babelOptions = {
  presets: ["babel-preset-gatsby"],
}

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

The moduleNameMapper object indicates to Jest how it should handle imports, much like Webpack rules. Because Jest is incapable of handling static file imports as mocks (dummy modules that don’t do anything), we need to manually create mocks that represent assets. For stylesheets such as layout.css, we’ll use the identity-obj-proxy package to handle this for us. For all other assets, however, we’ll need to create a directory in the project root named __mocks__ and add a manual mock named file-mock.js:

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

The testPathIgnorePatterns array tells Jest which directories to ignore—in this case, the node_modules.cache, and public directories, which aren’t things we need to unit-test.

The transformIgnorePatterns array is important, and may differ from other Jest implementations you are familiar with. Because the Gatsby framework by default includes ES6 code that remains untranspiled as a rule, we need to indicate to Jest that files containing code not meant to be transpiled shouldn’t be transformed by instructing Jest to ignore the Gatsby module. Otherwise, we’ll encounter an error that looks like this:

/my-app/node_modules/gatsby/cache-dir/gatsby-browser-entry.js:1
({"Object.<anonymous>":function(module,exports,require, >>>
__dirname,__filename,global,jest){import React from "react"
                                     ^^^^^^
SyntaxError: Unexpected token import

The globals object defines the __PATH_PREFIX__ string, which Gatsby sets itself and might be required by some components.

The testURL string identifies a valid URL rather than the default of about:blank, which doesn’t work with localStorage (an issue resolved in Jest 23.5.0 and later).

The setupFiles array permits us to enumerate files that should be included before tests are run. setupFiles cannot accept a function, so we’ll need to create another file, loadershim.js, in the project root containing the following code:

// loadershim.js
global.___loader = {
  enqueue: jest.fn()
}
TIP

If you need to modify your Babel configuration, perform those modifications in the jest-preprocess.js file. See Section 14 and consult the Gatsby Babel configuration guide for more information.

Before we proceed to writing and running unit tests in Jest for Gatsby, one final step that may be useful is to create a mock for the Gatsby module itself in the __mocks__ directory under the filename gatsby.js. If you’re using native Gatsby exports like Link or graphql, this will help you avoid errors down the road by ensuring they’re not absent:

// __mocks__/gatsby.js
const React = require("react")
const gatsby = jest.requireActual("gatsby")

module.exports = {
  ...gatsby,
  graphql: jest.fn(),
  Link: jest.fn().mockImplementation(
    // These props cannot be used on `a` elements
    ({
      activeClassName,
      activeStyle,
      getProps,
      innerRef,
      partiallyActive,
      ref,
      replace,
      to,
      ...rest
    }) =>
      React.createElement("a", {
        ...rest,
        href: to,
      })
  ),
  StaticQuery: jest.fn(),
  useStaticQuery: jest.fn(),
}

Mocking ensures Jest tests won’t hang when they come across the graphql function, Link component, and StaticQuery component in your Gatsby code.

NOTE

If you’re using TypeScript to write your Gatsby site, your Jest configuration will require additional dependencies to handle typings, different transformer configuration approaches, and, if you’re using tsconfig paths, other settings that are accounted for in the Jest with TypeScript section of the Gatsby documentation.


Posted

in

,

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *