Once your Gatsby implementation is complete, you’ll often find that you need to troubleshoot some issues before you can deploy your site to production. In Gatsby, it’s possible to do test-driven development (TDD) by writing unit tests in Jest and performing tests on various aspects of the development experience.
Even with a robust test architecture, however, sometimes Gatsby sites fail for obscure reasons and require a deeper level of troubleshooting and debugging. Fortunately, the Gatsby ecosystem provides a variety of tools for testing your sites ahead of time to help you avoid problems and debug them when issues arise. Before your site can go into production, it’s essential to ensure that it functions the way you intend.
In this chapter, we’ll cover testing strategies, including unit testing and component-level testing, and debugging tactics, including for static builds, server-side rendering issues, and other issues that arise during development.
Sometimes during development, it will be useful to spin up a local development server that provides local HTTPS support. For more information, consult the Gatsby documentation’s guide to local HTTPS.
Gatsby enables unit testing—testing that evaluates atomic units of functionality—with limited additional setup through Jest. The Jest framework can be used on its own for snapshot testing—testing that compares visual states of the application to track regressions in the user experience—and in conjunction with other libraries for unit testing of React components and components containing GraphQL queries.
But these aren’t the only kinds of testing that can be performed on Gatsby sites. The field of testing in web development has seen an explosion of innovation in recent years, with other techniques like visual regression testing (evaluating differences due to changes in visual application behavior) and end-to-end testing (evaluating whether an application functions correctly from beginning to end) made available by emerging ecosystems. In this section, we’ll cover how to unit-test with Jest and perform other types of testing with other tools.
It’s also possible to use Google Analytics in conjunction with Netlify to perform A/B or split testing (evaluating two different online states of an application to compare them against each other) on a Gatsby site, a procedure that is well beyond the scope of this book. Consult the A/B testing guide in the Gatsby documentation for a walkthrough.
Unit testing refers to the concept of testing and evaluating the quality of each unit of functionality made available by an implementation—in this case, a Gatsby site. Though Gatsby doesn’t offer a unit testing framework out of the box for efficiency reasons, Gatsby developers can use Jest to power unit testing with a minimal amount of overhead. However, because Gatsby’s build process is slightly unusual when it comes to the sort of JavaScript that Jest deals with, standard Jest implementations need some adjustment to work properly with Gatsby sites.
An exhaustive introduction to unit testing and Jest is beyond the scope of this brief overview, but you can consult the Jest documentation for more information, as well as the Using Jest example provided by Gatsby for an example using @testing-library/react
. If you’re using Emotion or Styled Components to power CSS-in-JavaScript for your Gatsby site, consult the Gatsby documentation’s guide to testing CSS-in-JS with Jest and the jest-styled-components
documentation.
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:
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
`
]
,
}
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:
const
babelOptions
=
{
presets
:
[
"babel-preset-gatsby"
],
}
module
.
exports
=
require
(
"babel-jest"
).
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:
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:
global
.
___loader
=
{
enqueue
:
jest
.
fn
()
}
If you need to modify your Babel configuration, you can perform those modifications in the jest-preprocess.js file. See Chapter 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:
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.
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.
Though a comprehensive accounting of writing Jest tests is outside the scope of this chapter, let’s write a rudimentary unit test that checks to make sure that our Gatsby blog starter correctly renders the expected <Bio />
component (src/components/bio.js) that is found in the starter.
First, we need to create a new directory within the same directory as our components (src/components) named __tests__. As an alternative, you can place the test file alongside the component in the same directory, with the extension .spec.js or .test.js (a matter of personal preference, since Jest correctly interprets both). In the following example, we place the test file in src/components/__tests__:
import
React
from
"react"
import
renderer
from
"react-test-renderer"
import
Bio
from
"../bio"
describe
(
"Bio"
,
()
=>
{
it
(
"renders correctly"
,
()
=>
{
const
tree
=
renderer
.
create
(
<
Bio
/>
)
.
toJSON
()
expect
(
tree
).
toMatchSnapshot
()
})
})
This test will employ react-test-renderer
to render the component, which we import into the test file. Thereafter, it will create a snapshot of the component that reflects its initial state on the first test run. Any future test runs will compare the resulting snapshots with the initial snapshot, allowing for quick detection of deltas that have generated regressions.
You can also use these unit tests in Jest to test components that accept props, such as the header component:
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
()
})
})
Writing unit tests will ensure that your code stays pristine and doesn’t introduce any regressions in functionality. But now that you know how to author unit tests in Jest, how can you run them?
If you inspect the package.json file that is generated by the Gatsby blog starter, you’ll find that there’s already an NPM script available known as test
—but it simply outputs an error message, as you can see in the following example:
"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"
:
"echo "Write tests! -> https://gatsby.dev/unit-testing" >>>
&& exit 1"
}
To ensure that this NPM script instead runs Jest unit tests that you’ve created, you need to modify the string associated with the test
key to jest
, as follows:
"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"
}
Now, you can run all your Jest unit tests by executing the following command in your terminal:
$
npm
test
To run unit tests together with a flag that enables a watch mode, which tracks changes to files and runs another regimen of unit tests when changes are detected in those files, use this command:
$
npm
test
--
--watch
When you run a round of unit tests successfully, you’ll see a notice in your terminal about snapshots being created, and alongside your __tests__ directory, a __snapshots__ directory will appear containing JSON representations of each component you tested (in our case, the <Bio />
and <Header />
components).
To have a convenient means of comparing snapshots against one another, you can check your __snapshots__ directory into your source repository so you don’t publish any new code changes without verifying that the snapshots match the intended result. An additional advantage of tracking your snapshots in source control is that you have a record of how your units of functionality have evolved over time.
To update the initial snapshot so new changes aren’t considered regressions, you can run another round of tests and instruct Jest to update the original snapshot to the new state:
$
npm
test
--
-u
Though these unit tests are powerful for verifying that your snapshots match the expected rendered output, what about situations where you need to do more involved checking to ensure that the document object model (DOM) of your components matches the rendered output? For this, you can use a tool such as @testing-library/react
, which enables you to write both unit and integration tests for React components (which all Gatsby components are, in the end).
If you’re using a continuous integration (CI) system that runs tests for you, like CircleCI or Travis, those systems typically require snapshots to be checked into source control.
The @testing-library/react
library includes several helpers on top of react-dom
and react-dom/test-utils
to aid in ensuring that whenever the implementation (though not the functionality) of your component evolves, your tests won’t break. If you followed the steps in the previous section to install and configure Jest, you can pick up right here. Otherwise, check to ensure that you have Jest installed and configured correctly for your Gatsby site.
First, you’ll need to install several additional development dependencies (devDependencies
):
$
npm
install
--save-dev
@testing-library/react
@testing-library/jest-dom
At the root of your project, in the same directory as package.json and gatsby-config.js, create a new file named setup-test-env.js containing the following import:
import
"@testing-library/jest-dom/extend-expect"
Because this JavaScript file is executed every time Jest runs a test, you don’t need to add this import
statement to every single test file. Now, you need to make Jest aware of this file and indicate its location. If your jest.config.js file is in the same state it was in at the end of the previous section, all you need to do is add a new key, setupFilesAfterEnv
, to the module.exports
object and identify the file you just created:
module
.
exports
=
{
// Other Jest configuration.
setupFiles
:
[
`<rootDir>/loadershim.js`
],
setupFilesAfterEnv
:
[
"<rootDir>/setup-test-env.js"
],
}
Now, you can write tests that use @testing-library/react
instead of react-test-renderer
, and you have access to a much richer range of methods to use to introspect the rendered DOM elements and ensure they match your snapshots. Here is one example of a rudimentary test that ensures our <Bio />
component contains a particular string as opposed to the child elements it comes with by default in the unmodified Gatsby blog starter:
import
React
from
"react"
import
{
render
}
from
"@testing-library/react"
const
Bio
=
()
=>
(
<
div
className
=
"bio"
data
-
testid
=
"main-bio"
>
Hello
!
This
is
my
bio
!
<
/div>
)
test
(
"Displays the correct title"
,
()
=>
{
const
{
getByTestId
}
=
render
(
<
Bio
/>
)
// The next line describes our assertion.
expect
(
getByTestId
(
"main-bio"
))
.
toHaveTextContent
(
"Hello! This is my bio!"
)
})
We’ve covered unit tests at a high level in this section, but that doesn’t mean unit testing is the only type of testing you should be concerned with. For small-scale testing, unit tests do the trick, but sometimes it’s important to have a more holistic view of the visual design of your components as well as a sense of how your site performs end-to-end across multiple pages. We’ll turn our attention to these two types of tests next.
Gatsby pages and components containing GraphQL queries require special handling, covered in the Gatsby documentation’s guide to testing components with GraphQL. For additional information, consult the documentation for @testing-library/react
and custom Jest matchers in @testing-library/jest-dom
.
Though unit testing is powerful for situations where you need to maintain functional parity over time, sometimes a visual testing approach can be even more fruitful for maintaining a high-quality Gatsby implementation. One tool that has become popular for visual inspection and testing of JavaScript components is Storybook, a development environment for user interface components that allows developers to see their components across various states and understand what components they can leverage and what props they accept.
To install and configure Storybook, the quickest way to get started is to use the npx
command, which comes packaged with NPM. NPX, used to execute packages (as opposed to the NPM installer), is useful because it will scaffold a directory structure with default configuration already in place to maximize productivity. Execute the following command in the root of your project:
$
npx
-p
@storybook/cli
sb
init
If you’re using a version of NPM older than 5.2.0, because NPX is a newer addition to NPM, you will need to run the following instead:
$
npm
install
-g
@storybook/cli
$
sb
init
Though Storybook will scaffold a fully constructed set of directories and files within them in the .storybook directory, Gatsby requires some adjustments to Storybook’s default configuration. If you open .storybook/main.js, you’ll find the following configuration:
module
.
exports
=
{
stories
:
[
"../src/**/*.stories.mdx"
,
"../src/**/*.stories.@(js|jsx|ts|tsx)"
],
addons
:
[
"@storybook/addon-actions"
,
"@storybook/addon-links"
],
}
You’ll need to extend this Storybook configuration to embrace Gatsby’s own ES6 code that remains untranspiled, similarly to how we configured Jest to do so earlier in this chapter. Change your Storybook configuration to the following, adding webpackFinal
after the lines in the previous example, as follows:
module
.
exports
=
{
stories
:
[
"../src/**/*.stories.mdx"
,
"../src/**/*.stories.@(js|jsx|ts|tsx)"
],
addons
:
[
"@storybook/addon-actions"
,
"@storybook/addon-links"
],
>>>
webpackFinal
:
async
config
=>
{
config
.
module
.
rules
[
0
].
exclude
=
[
/node_modules/(?!(gatsby)/)/
]
config
.
module
.
rules
[
0
].
use
[
0
].
loader
=
require
.
resolve
(
"babel-loader"
)
config
.
module
.
rules
[
0
].
use
[
0
].
options
.
presets
=
[
require
.
resolve
(
"@babel/preset-react"
),
require
.
resolve
(
"@babel/preset-env"
),
]
config
.
module
.
rules
[
0
].
use
[
0
].
options
.
plugins
=
[
require
.
resolve
(
"@babel/plugin-proposal-class-properties"
),
require
.
resolve
(
"babel-plugin-remove-graphql-queries"
),
]
config
.
resolve
.
mainFields
=
[
"browser"
,
"module"
,
"main"
]
return
config
},
}
These lines configure Webpack to handle the Gatsby module correctly and to ensure that Storybook uses Gatsby’s ES6 entry point (which prepopulates dependencies) rather than the default CommonJS entry point (which executes dependencies on demand). Now, you need to provide a configuration file that will add certain global parameters and Storybook decorators to ensure Storybook works with Gatsby. Replace the contents of the file named preview.js in the .storybook directory with the following code:
import
{
action
}
from
"@storybook/addon-actions"
// This prevents Gatsby Link from triggering errors in Storybook.
global
.
___loader
=
{
enqueue
:
()
=>
{},
hovering
:
()
=>
{},
}
// This global variable prevents the "__BASE_PATH__ is not defined" error
// inside Storybook.
global
.
__BASE_PATH__
=
"/"
// Navigating through a Gatsby app using gatsby-link or any other
// gatsby component will use the `___navigate` method. In Storybook
// it makes more sense to log an action than doing an actual navigate.
// Check out the actions addon docs for more info:
// https://github.com/storybookjs/storybook/tree/master/addons/actions.
window
.
___navigate
=
pathname
=>
{
action
(
"NavigateTo:"
)(
pathname
)
}
export
const
parameters
=
{
actions
:
{
argTypesRegex
:
"^on[A-Z].*"
},
controls
:
{
matchers
:
{
color
:
/(background|color)$/i
,
date
:
/Date$/
,
},
},
}
With both of these configuration files in place, you can now write Storybook stories. Storybook will seek any files with a .stories.js extension and load them automatically in your Storybook interface for you. For Gatsby components, these stories can live alongside the components in the same directory. But for Gatsby pages, since Gatsby restricts what JavaScript files are permitted in the src/pages directory, you’ll need to create a __stories__ directory in the src directory.
A full walkthrough of Storybook is outside the scope of this book, but as an example adapted from the Gatsby documentation, here’s a Storybook story, which in Storybook parlance describes a rendered component that is displayed in the Storybook interface:
import
React
from
"react"
export
default
{
title
:
"Bio"
,
}
export
const
bioStory
=
()
=>
(
<
div
style
=
{{
padding
:
"1rem"
,
backgroundColor
:
"#ccc"
}}
>
<
h1
style
=
{{
color
:
"blue"
}}
>
Hello
world
and
Storybook
!<
/h1>
<
/div>
)
For a comprehensive introduction to Storybook, consult the Storybook documentation and Mathspy’s Gatsby starter containing both Jest and Storybook. The Gatsby documentation also has information about how to work with Storybook 4 and TypeScript implementations.
Visual testing and unit testing both permit small-scale observation of how quality changes over time across individual Gatsby components and pages. However, there are often situations where, as a Gatsby developer, it will be desirable to perform end-to-end (e2e) testing that involves not only single pages and components but navigating through an entire Gatsby site with multiple pages and components involved in one test.
This is particularly important for accessibility testing when it comes to accessible client-side routing, as an e2e test can reveal when navigating between pages isn’t working properly due to a missing Reach route change or other issue. Though there are multiple systems commonly used for e2e testing, Cypress is a common choice.
Because e2e testing typically requires a second parallel server to be running alongside Gatsby’s local development server, you’ll want to use the start-server-and-test
library to manage both in parallel. Add the following to your development dependencies (devDependencies
):
$
npm
install
--save-dev
cypress
start-server-and-test
@testing-library/cypress
Cypress comes with the ability to configure certain global settings. Do that now by creating a new cypress.json file in your project root:
{
"baseUrl"
:
"http://localhost:8000/"
}
This baseUrl
configuration will ensure that every URL that Cypress visits or issues a request to will be prefixed with the correct domain for our Gatsby development server. Next, you need to add Cypress scripts to our package.json to ensure you can run Cypress tests from the terminal:
{
"scripts"
:
{
"develop"
:
"gatsby develop"
,
"test:jest"
:
"jest"
,
"cy:open"
:
"cypress open"
,
"test:e2e"
:
"start-server-and-test develop http://localhost:8000 cy:open"
}
}
If you’re using Cypress in the context of a CI provider like CircleCI or Travis, you will need to use the cypress run
command instead of cypress open
:
"cy:run"
:
"cypress run"
,
To kick off a Cypress test, execute the following after installing other dependencies required for Cypress:
$
npm
run
test
:e2e
To distinguish between Jest unit tests and end-to-end Cypress tests, you may want to consider changing the npm run test
command to npm run test:jest
. This will avoid any possible confusion with an npm run test:e2e
command for Cypress.
You’ll see a new directory named cypress created within the root of your project.
If you’re running your Gatsby development server with HTTPS enabled (i.e., gatsby develop --https
), you will need to indicate to start-server-and-test
that it needs to disable SSL certificate checks, because these will cause Cypress to hang. In your package.json, your test:e2e
command should be changed to the following to include an environment variable that disables certificate checks:
"test:e2e"
:
"START_SERVER_AND_TEST_INSECURE=1 >>>
start-server-and-test develop >>>
http://localhost:8000 cy:open"
One of the most common use cases for end-to-end testing with Cypress is accessibility testing, which requires a library such as Axe. Though building in automated accessibility testing is a best practice for Gatsby sites, it’s also a best practice to perform at least some manual accessibility testing lest something fall through the cracks.
You can install those additional Axe dependencies now:
$
npm
install
--save-dev
cypress-axe
axe-core
Because you already ran Cypress once, you can open the already created cypress/support/index.js file and import the additional dependencies you now need, including @testing-library/cypress
:
import
"./commands"
import
"cypress-axe"
import
"@testing-library/cypress/add-commands"
Though Cypress will look for tests within the cypress/integration directory by default, accessibility tests that visit multiple pages are considered end-to-end tests and should be placed in a new cypress/e2e directory as a best practice. In this case, you’ll need to configure an integrationFolder
in your Cypress configuration as follows:
{
"baseUrl"
:
"http://localhost:8000/"
,
"integrationFolder"
:
"cypress/e2e"
}
To finish our exploration of Cypress end-to-end testing, let’s create a rudimentary test that checks for the absence of accessibility violations on the initial load of the home page of our example Gatsby site, which in this chapter is our Gatsby blog starter. Create a new file in the cypress/e2e directory named accessibility.test.js with the following contents:
/// <reference types="Cypress" /> describe("Accessibility tests", () => { beforeEach(() => { cy.visit("/").get("main").injectAxe() }) it("Has no detectable accessibility violations on load", () => { cy.checkA11y() }) it("Navigates to blog post and checks for accessibility violations", () => { cy.findByText(/new beginnings/i) .click() .checkA11y() }) })
This comment facilitates autocompletion if you’re using certain IDEs (such as those with IntelliSense).
As the cy.visit()
method suggests, Cypress visits the home page of your Gatsby site and waits until the page load is complete. The subsequent chained methods find the <main>
HTML element (using cy.get()
, as Gatsby may “finish” loading too quickly), where the Axe accessibility testing API is injected and made available.
The cy.checkA11y()
method performs a check for any accessibility errors on the home page.
The cy.findByText()
method finds the first blog post displayed in the Gatsby blog starter and navigates to it, performing another check for accessibility errors on that page.
You can extend this end-to-end accessibility test by working through some additional assertions using the should
method to check whether a given link has text legible to screen readers and has an href
attribute, as is expected of all links:
describe
(
"Accessibility tests"
,
()
=>
{
// Previous assertions.
it
(
"Focuses on the header link and asserts its attributes"
,
()
=>
{
cy
.
findAllByText
(
"Gatsby Starter Blog"
).
focus
()
cy
.
focused
()
.
should
(
"have.text"
,
"Gatsby Starter Blog"
)
.
should
(
"have.attr"
,
"href"
,
"/"
)
})
})
A full examination of Cypress and Axe is beyond the scope of this brief introduction to end-to-end testing, but Cypress offers a getting started guide, core concepts page, and API reference. In addition, Gatsby provides a Cypress example and a guide to customizing Axe options. For more information about Axe, see the Axe documentation.
Like other development frameworks, Gatsby isn’t free of bugs, whether they are attributable to developer error or something upstream in the framework. At various points in the Gatsby development experience, you might run into unexpected problems with static builds, rendering, the build process itself, the Gatsby cache, or asynchronous lifecycle methods. Gatsby also offers a performance tracing tool that you can use to identify areas of your site that require more optimization.
The official Gatsby documentation tracks a range of frequent issues that developers encounter during Gatsby development and common troubleshooting solutions to resolve them.
Because Gatsby’s static build process ultimately generates a series of HTML files that maximize the speed at which Gatsby sites load in the browser, some issues can be difficult to diagnose. In many cases, issues surrounding static builds involve inaccessible globals or Webpack bundling issues. The Gatsby documentation outlines four specific cases that are common in Gatsby sites:
Undefined browser globals. Because JavaScript can be executed on the server side or the client side, if your code references the window
or document
object, those objects may be unavailable except when the Gatsby site is loaded in a browser. During the build process, you may run into errors such as window is not defined
, as it is undefined
before the site reaches the browser. There are two ways to handle this:
If your code requires the use of the window
or document
object in the browser, perform a check to ensure window
or document
is defined so that the code within the check is only executed in the browser, as follows:
if
(
typeof
window
!==
`undefined`
)
{
// Your window-reliant code here.
}
If the offending code with references to the window
or document
object is within the render
function of a React component, you’ll need to relocate that code into a componentDidMount
lifecycle method in React or, if you’re using React hooks, into a useEffect
hook implementation to prevent its execution outside the browser.
Improperly exported pages. Because Gatsby automatically generates pages based on the contents of the pages directory, you must ensure that all files within that directory are exporting either a React component or a string. Otherwise, Gatsby’s automatic page generation will fail.
Collision of import
and require
in a single file. In Webpack 4, the bundler no longer permits the use of both import
and require
statements within the same file. Instead, Gatsby recommends using only import
statements within Gatsby files, including gatsby-ssr.js and gatsby-browser.js.
Improper hydration. Gatsby allows for client-side functionality to proceed by hydrating rendered HTML with React. If this hydration doesn’t happen correctly, you will run into inconsistencies between the results of the gatsby develop
and gatsby build
commands. If you discover inconsistencies between the build results, it’s likely that some logic in gatsby-ssr.js or gatsby-browser.js is causing a mismatch between the HTML generated by the server and that understood by the client.
One edge case not enumerated in this list relates to NPM modules that are used as dependencies in Gatsby sites and refer to the window
object. Because these are upstream dependencies, if you report the issue and request a check on the window
object it may take some time before the maintainer responds. To avoid hacking into the module itself, which compromises dependency management, in gatsby-node.js you can customize your Webpack configuration such that during server-side rendering, the module is replaced with an empty dummy module:
exports
.
onCreateWebpackConfig
=
({
stage
,
loaders
,
actions
})
=>
{
if
(
stage
===
"build-html"
)
{
actions
.
setWebpackConfig
({
module
:
{
rules
:
[
{
test
:
/module-without-window-defined-check/
,
use
:
loaders
.
null
(),
},
],
},
})
}
}
An alternative to this approach is to use the loadable-components
package, which allows developers to use code splitting to force a module to dynamically load only in the browser and not during server-side rendering.
Because both the gatsby build
and gatsby develop
scripts are technically Node.js applications at their core, you can debug issues that occur within the execution of those scripts using commonly available standard approaches and tools for Node.js application development. Using Node.js debugging tools can be useful for situations where you need to identify a problem area in a gatsby-node.js file or another file that influences the build process or instantiation of the local development server.
For Gatsby developers familiar with debugging JavaScript in the browser, the console
object is available with a console.log()
method to print any values that need verification or introspection to the browser console during execution:
console
.
log
(
anObject
)
Because the console
object is also built into Node.js, you can add an invocation of console.log()
to any executed code in your gatsby-node.js file or any other file—including Gatsby pages and components—to print the variable’s value into the terminal output, just like in the browser console. Consider the following example from gatsby-source-filesystem
, in which we print the args
variable to the Node.js console in the terminal:
const
{
createFilePath
}
=
require
(
"gatsby-source-filesystem"
)
exports
.
onCreateNode
=
args
=>
{
console
.
log
(
args
)
const
{
actions
,
node
}
=
args
if
(
node
.
internal
.
type
===
"MarkdownRemark"
)
{
const
{
createNodeField
}
=
actions
const
value
=
createFilePath
({
node
,
getNode
})
createNodeField
({
name
:
`slug`
,
node
,
value
,
})
}
}
Observe the difference between console.log()
statements that are executed at build time and can be seen in the terminal, like in the gatsby-node.js file, and those statements that are executed on the client side, whose results will display in the browser console instead of the terminal.
If you use VS Code Debugger or Chrome DevTools for Node during the development process, these tools can help you perform similar debugging to that which you can do in a browser by connecting a debugger, even including the use of breakpoints. Consult the Gatsby documentation for in-depth guides to debugging Node.js with both VS Code Debugger and Chrome DevTools for Node.
Gatsby performs server-side rendering as part of its build process to facilitate faster page loads on the browser. Hooking into this rendering process involves creating or modifying the gatsby-ssr.js file. One of the server-side rendering APIs commonly leveraged during the build process is the replaceRenderer
API, which can be used to customize or otherwise adjust the ways in which Gatsby renders out the contents of your site in static form. The most common use cases for this API generally involve the use of Redux, Styled Components, or other CSS-in-JavaScript libraries that need to influence the markup rendered on the server side.
The replaceRenderer
API is employed by various Gatsby plugins in the ecosystem to extend Gatsby so it supports these libraries, or any custom code that needs to modify how Gatsby generates its initial HTML output for the browser. This logic is executed solely during the build process (i.e., when gatsby build
is executed), not during local development (gatsby develop
). Sometimes, when multiple plugins require usage of the replaceRenderer
API through their respective gatsby-ssr.js files, conflicts can occur between them.
If multiple plugins are simultaneously using the API at the same time, during the build process you’ll encounter an error that looks like the following, where default-site-plugin
represents the local Gatsby site’s gatsby-ssr.js file, not the plugin’s:
warning replaceRenderer API found in these plugins:
warning gatsby-plugin-one, default-site-plugin
warning This might be an error, see: https://www.gatsbyjs.com/docs/debugging-
replace-renderer-api/
warning Duplicate replaceRenderer found, skipping gatsby-ssr.js for
plugin:
gatsby-plugin-one
The easiest way to correct an issue with conflicting gatsby-ssr.js files is to identify the offending replaceRenderer
API code in the plugin’s gatsby-ssr.js file and copy it into your Gatsby site’s own gatsby-ssr.js file. This needs to happen because only the gatsby-ssr.js file for the site being built will be executed during the build process, which only accepts a single gatsby-ssr.js file.
For a common real-world example of how to copy the replaceRenderer
API code in a plugin’s gatsby-ssr.js file into the Gatsby site’s native gatsby-ssr.js file, consult the example involving Redux and Styled Components in the Gatsby documentation.
Gatsby contains its own internal cache that is used to accelerate plugin handling, and especially the data sourcing process. Sometimes the Gatsby cache can fail, manifesting in strange and perhaps silent errors involving stale content, where the most up-to-date content doesn’t display despite an upstream content update, and recent modifications to plugin code that don’t appear to execute.
Gatsby uses a .cache directory to cache certain elements of the Gatsby site. Starting with Gatsby version 2.1.1, the gatsby clean
command is available to clear out the Gatsby cache and ensure the most recent versions of your plugin code and content are represented in your site:
$
gatsby
clean
Prior to v2.1.1, an NPM script was required in the site’s package.json file, as follows:
{
"scripts"
:
{
"clean"
:
"rm -rf .cache"
}
}
To run this NPM script for versions 2.1.0 and earlier, execute:
$
npm
run
clean
For Gatsby developers who are used to previous paradigms of JavaScript programming, the introduction of specific syntax for asynchronicity into JavaScript may be a rude awakening. As of Node.js version 8, Node.js can natively handle the async
and await
keywords, which are used to identify functions that should be treated asynchronously but are written as if they were synchronous. The async
and await
keywords are particularly useful for situations where consecutive Promise
s were chained together in older JavaScript, which often led to confusing and hard-to-read code.
Within Gatsby, certain lifecycle methods, like createPages
in Gatsby’s Node APIs, are presumed by the framework to be asynchronous. This means it is generally assumed that a value may not immediately be available upon the execution of the createPages
logic. Methods like createPages
will eventually resolve to a value (or not resolve, throwing an error) represented by a Promise
. In asynchronous JavaScript with Promise
s, we wait for the Promise
to resolve to a usable value, and in Gatsby’s case, the lifecycle method in question (such as createPages
) is considered complete once the Promise
resolves.
Gatsby’s Node APIs handle a great deal of asynchronous logic, such as requests for data from external sources, invocations of graphql
, and more. For this reason, Gatsby developers may encounter difficult-to-debug errors when writing logic in gatsby-node.js that does not correctly return Promise
s. The resolutions of these promises may occur after the lifecycle method is considered by Gatsby to be complete, thus returning a value that was expected but now is never used.
When writing logic in gatsby-node.js, pay close attention to whether a method is expected to be asynchronous or not. For instance, in the following example, not including the await
keyword shown ahead of the invocation of graphql
would result in absent data, since createPages
will not wait for the value representing the result of graphql
to be returned:
exports
.
createPages
=
async
function
(
{
actions
,
graphql
}
)
{
await
graphql
(
`
{ allMarkdownRemark { edges { node { fields { slug } } } } }
`
)
.
then
(
res
=>
{
res
.
data
.
allMarkdownRemark
.
edges
.
forEach
(
edge
=>
{
const
slug
=
edge
.
node
.
fields
.
slug
actions
.
createPage
(
{
path
:
slug
,
component
:
require
.
resolve
(
`
./src/templates/article.js
`
)
,
context
:
{
slug
}
,
}
)
}
)
}
)
}
There are also scenarios where it is necessary to wait for a series of unconnected Promise
s to resolve before proceeding. In JavaScript, the Promise.all()
method encapsulates multiple asynchronous actions, such as a data fetch (e.g., fetch().then()
) that represents a Promise
but needs to query multiple APIs or data sources. Consider the following example, in which we retrieve data from multiple APIs:
const
fetch
=
require
(
"node-fetch"
)
exports
.
createPages
=
async
function
({
actions
,
graphql
})
{
const
[
siteAData
,
siteBData
]
=
await
Promise
.
all
([
fetch
(
"https://site-a.com/api/articles"
).
then
(
res
=>
res
.
json
()),
fetch
(
"https://site-b.com/api/products"
).
then
(
res
=>
res
.
json
()),
])
// Further logic with actions.createPage.
}
Because one of the fetch
operations may conclude before the other, we need to ensure that Gatsby awaits the resolution of all Promise
s contained therein before proceeding and marking the createPages
lifecycle method as complete.
Getting a Gatsby site ready to go live in production isn’t just a matter of putting the finishing touches on implementation. During the development process, it’s often the case that small bugs or larger issues creep into code that can stymie even the best-planned development cycle. For this reason, Gatsby provides a robust set of tools for testing Gatsby sites during implementation and debugging issues as they arise.
Fortunately, many of the practices we’ve outlined in this chapter are foundational to JavaScript or Node.js in addition to Gatsby itself, but that doesn’t mean that developers building Gatsby sites should give testing and debugging short shrift on the journey to a live Gatsby site. The tools and techniques covered in this chapter will serve you particularly well as we move on to our next topic, deploying Gatsby sites, where it’s a bit less permissible for things to go wrong—especially when building for customers.
35.170.81.33