Skip to content

Commit 401df07

Browse files
DSchaupieh
authored andcommitted
fix: ensure babel-preset-gatsby can be used with unit tests (#9629)
This PR does a few things, namely: - Fixes a few things with the test stage, e.g. uses commonjs modules for test running, uses the current node version for @babel/preset-env, etc. - Fixes up docs slightly for the unit testing guide - Fixes #9614 <!-- Q. Which branch should I use for my pull request? A. Use `master` branch (probably). Q. Which branch if my change is a bug fix for Gatsby v1? A. In this case, you should use the `v1` branch Q. Which branch if I'm still not sure? A. Use `master` branch. Ask in the PR if you're not sure and a Gatsby maintainer will be happy to help :) Note: We will only accept bug fixes for Gatsby v1. New features should be added to Gatsby v2. Learn more about contributing: https://www.gatsbyjs.org/docs/how-to-contribute/ -->
1 parent bbf1f15 commit 401df07

30 files changed

+1240
-121
lines changed

docs/docs/unit-testing.md

Lines changed: 87 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -26,32 +26,25 @@ npm install --save-dev jest babel-jest react-test-renderer identity-obj-proxy 'b
2626
```
2727

2828
Because Gatsby handles its own Babel configuration, you will need to manually
29-
tell Jest to use `babel-jest`. The easiest way to do this is to add a `"jest"`
30-
section in your `package.json`. You can set up some useful defaults at the same
31-
time:
32-
33-
```json:title=package.json
34-
"jest": {
35-
"transform": {
36-
"^.+\\.jsx?$": "<rootDir>/jest-preprocess.js"
37-
},
38-
"testRegex": "/.*(__tests__\\/.*)|(.*(test|spec))\\.jsx?$",
39-
"moduleNameMapper": {
40-
".+\\.(css|styl|less|sass|scss)$": "identity-obj-proxy",
41-
".+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js"
42-
},
43-
"testPathIgnorePatterns": ["node_modules", ".cache"],
44-
"transformIgnorePatterns": [
45-
"node_modules/(?!(gatsby)/)"
46-
],
47-
"globals": {
48-
"__PATH_PREFIX__": ""
49-
},
50-
"testURL": "http://localhost",
51-
"setupFiles": [
52-
"<rootDir>/loadershim.js"
53-
]
54-
}
29+
tell Jest to use `babel-jest`. The easiest way to do this is to add a `jest.config.js`. You can set up some useful defaults at the same time:
30+
31+
```json:title=jest.config.js
32+
module.exports = {
33+
"transform": {
34+
"^.+\\.jsx?$": "<rootDir>/jest-preprocess.js"
35+
},
36+
"moduleNameMapper": {
37+
".+\\.(css|styl|less|sass|scss)$": "identity-obj-proxy",
38+
".+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js"
39+
},
40+
"testPathIgnorePatterns": ["node_modules", ".cache"],
41+
"transformIgnorePatterns": ["node_modules/(?!(gatsby)/)"],
42+
"globals": {
43+
"__PATH_PREFIX__": ""
44+
},
45+
"testURL": "http://localhost",
46+
"setupFiles": ["<rootDir>/loadershim.js"]
47+
}
5548
```
5649

5750
The `transform` section tells Jest that all `js` or `jsx` files need to be
@@ -128,11 +121,47 @@ needed at first, but will make things a lot easier if you want to test
128121
components that use `Link` or GraphQL.
129122

130123
```js:title=__mocks__/gatsby.js
124+
const React = require("react")
131125
const gatsby = jest.requireActual("gatsby")
132-
module.exports = { ...gatsby, graphql: jest.fn(), Link: "Link" }
126+
127+
module.exports = {
128+
...gatsby,
129+
graphql: jest.fn(),
130+
Link: jest.fn().mockImplementation(({ to, ...rest }) =>
131+
React.createElement("a", {
132+
...rest,
133+
href: to,
134+
})
135+
),
136+
StaticQuery: jest.fn(),
137+
}
138+
```
139+
140+
This mocks the `graphql()` function, `Link` component, and `StaticQuery` component.
141+
142+
One more issue that you may encounter is that some components expect to be able
143+
to use the `location` prop that is passed in by `Router`. You can fix this by
144+
manually passing in the prop:
145+
146+
```js:title=src/__tests__/index.js
147+
import React from "react"
148+
import renderer from "react-test-renderer"
149+
import BlogIndex from "../pages/index"
150+
151+
describe("BlogIndex", () => {
152+
it("renders correctly", () => {
153+
const location = {
154+
pathname: "/",
155+
}
156+
157+
const tree = renderer.create(<BlogIndex location={location} />).toJSON()
158+
expect(tree).toMatchSnapshot()
159+
}))
160+
})
133161
```
134162

135-
This mocks the `graphql()` function and `Link` component.
163+
For more information on testing page components, be sure to read the docs on
164+
[testing components with GraphQL](/docs/testing-components-with-graphql/)
136165

137166
## Writing tests
138167

@@ -150,11 +179,12 @@ import React from "react"
150179
import renderer from "react-test-renderer"
151180
import Bio from "./Bio"
152181

153-
describe("Bio", () =>
182+
describe("Bio", () => {
154183
it("renders correctly", () => {
155184
const tree = renderer.create(<Bio />).toJSON()
156185
expect(tree).toMatchSnapshot()
157-
}))
186+
})
187+
})
158188
```
159189

160190
This is a very simple snapshot test, which uses `react-test-renderer` to render
@@ -220,95 +250,30 @@ config. First install `ts-jest`:
220250
npm install --save-dev ts-jest
221251
```
222252
223-
Then edit the Jest config in your `package.json` to match this:
224-
225-
```json:title=package.json
226-
"jest": {
227-
"transform": {
228-
"^.+\\.tsx?$": "ts-jest",
229-
"^.+\\.jsx?$": "<rootDir>/jest-preprocess.js"
230-
},
231-
"testRegex": "(/__tests__/.*\\.([tj]sx?)|(\\.|/)(test|spec))\\.([tj]sx?)$",
232-
"moduleNameMapper": {
233-
".+\\.(css|styl|less|sass|scss)$": "identity-obj-proxy",
234-
".+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js"
235-
},
236-
"moduleFileExtensions": [
237-
"ts",
238-
"tsx",
239-
"js",
240-
"jsx",
241-
"json",
242-
"node"
243-
],
244-
"testPathIgnorePatterns": ["node_modules", ".cache"],
245-
"transformIgnorePatterns": [
246-
"node_modules/(?!(gatsby)/)"
247-
],
248-
"globals": {
249-
"__PATH_PREFIX__": ""
250-
},
251-
"testURL": "http://localhost",
252-
"setupFiles": [
253-
"<rootDir>/loadershim.js"
254-
]
255-
}
256-
```
257-
258-
## Testing components with Router
259-
260-
When you test components they are not in a `Router`, meaning they don't have
261-
access to some context and props that they may be expecting. The most common of
262-
these is the `Link` component. In the example above we mock the `Link` component
263-
as a string, which is the simplest solution and works for most uses. However
264-
sometimes you might want to test with the real `Link` component. As of v2,
265-
Gatsby uses `@reach/router` for navigation, which is good at handling test
266-
environments, and unlike React Router is happy to render `Link`s outside of a
267-
`Router` context. However there is a small issue related to the `gatsby` mock.
268-
We can use a small workaround to avoid an error.
269-
270-
First, remove the `Link` mock from `gatsby`:
271-
272-
```js:title=__mocks__/gatsby.js
273-
const gatsby = jest.requireActual("gatsby")
274-
module.exports = { ...gatsby, graphql: jest.fn() }
275-
```
276-
277-
While the `Link` component is exported by the main `gatsby` package, it is
278-
actually defined in `gatsby-link`. That in turn uses `parsePath()` from
279-
`gatsby`, which causes module resolution issues. Fortunately it's an easy fix.
280-
You need to create a mock for `gatsby-link`, even though it will actually be the
281-
real module. You do this so that you can tell it to not try and use the mock
282-
`gatsby`:
283-
284-
```js:title=__mocks__/gatsby-link.js
285-
jest.unmock("gatsby")
286-
module.exports = jest.requireActual("gatsby-link")
287-
```
288-
289-
One more issue that you may encounter is that some components expect to be able
290-
to use the `location` prop that is passed in by `Router`. You can fix this by
291-
manually passing in the prop:
292-
293-
```js:title=src/__tests__/index.js
294-
import React from "react"
295-
import renderer from "react-test-renderer"
296-
import BlogIndex from "../pages/index"
297-
298-
describe("BlogIndex", () =>
299-
it("renders correctly", () => {
300-
const location = {
301-
pathname: "/",
302-
}
303-
304-
const tree = renderer.create(<BlogIndex location={location} />).toJSON()
305-
expect(tree).toMatchSnapshot()
306-
}))
253+
Then update the configuration in `jest.config.js`, like so:
254+
255+
```json:title=jest.config.js
256+
module.exports = {
257+
"transform": {
258+
"^.+\\.tsx?$": "ts-jest",
259+
"^.+\\.jsx?$": "<rootDir>/jest-preprocess.js"
260+
},
261+
"testRegex": "(/__tests__/.*\\.([tj]sx?)|(\\.|/)(test|spec))\\.([tj]sx?)$",
262+
"moduleNameMapper": {
263+
".+\\.(css|styl|less|sass|scss)$": "identity-obj-proxy",
264+
".+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js"
265+
},
266+
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"],
267+
"testPathIgnorePatterns": ["node_modules", ".cache"],
268+
"transformIgnorePatterns": ["node_modules/(?!(gatsby)/)"],
269+
"globals": {
270+
"__PATH_PREFIX__": ""
271+
},
272+
"testURL": "http://localhost",
273+
"setupFiles": ["<rootDir>/loadershim.js"]
274+
}
307275
```
308276
309-
For more information on testing page components, be sure to read the docs on
310-
[testing components with GraphQL](/docs/testing-components-with-graphql/)
311-
312277
## Other resources
313278
314279
If you need to make changes to your Babel config, you can edit the config in
@@ -318,3 +283,8 @@ though remember you may need to install the Babel 7 versions. See
318283
319284
For more information on Jest testing, visit
320285
[the Jest site](https://jestjs.io/docs/en/getting-started).
286+
287+
For an example encapsulating all of these techniques--and a full unit test suite with [react-testing-library][react-testing-library], check out the [using-jest][using-jest] example.
288+
289+
[using-jest]: https://github.com/gatsbyjs/gatsby/tree/master/examples/using-jest
290+
[react-testing-library]: https://github.com/kentcdodds/react-testing-library

examples/using-jest/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
public
2+
.cache
3+
node_modules

examples/using-jest/.prettierrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"semi": false,
3+
"singleQuote": true,
4+
"trailingComma": "es5"
5+
}

examples/using-jest/LICENSE

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2015 gatsbyjs
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
22+

examples/using-jest/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<p align="center">
2+
<a href="https://www.gatsbyjs.org">
3+
<img alt="Gatsby" src="https://www.gatsbyjs.org/monogram.svg" width="60" />
4+
</a>
5+
</p>
6+
<h1 align="center">
7+
using Jest
8+
</h1>
9+
10+
Kick off your next Gatsby app with some great testing practices enabled via [Jest][jest], [react-testing-library][react-testing-library], and of course, [Gatsby][gatsby] 💪
11+
12+
Check out the [unit testing doc][unit-testing-doc] for further info!
13+
14+
[jest]: https://jestjs.io/
15+
[react-testing-library]: https://github.com/kentcdodds/react-testing-library
16+
[gatsby]: https://gatsbyjs.org
17+
[unit-testing-doc]: https://www.gatsbyjs.org/docs/unit-testing/
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = 'test-file-stub'
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const React = require('react')
2+
const gatsby = jest.requireActual('gatsby')
3+
4+
module.exports = {
5+
...gatsby,
6+
graphql: jest.fn(),
7+
Link: jest.fn().mockImplementation(({ to, ...rest }) =>
8+
React.createElement('a', {
9+
...rest,
10+
href: to,
11+
})
12+
),
13+
StaticQuery: jest.fn(),
14+
}

examples/using-jest/gatsby-config.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
module.exports = {
2+
siteMetadata: {
3+
title: 'Gatsby Default Starter',
4+
},
5+
plugins: [
6+
'gatsby-plugin-react-helmet',
7+
{
8+
resolve: `gatsby-source-filesystem`,
9+
options: {
10+
name: `images`,
11+
path: `${__dirname}/src/images`,
12+
},
13+
},
14+
'gatsby-transformer-sharp',
15+
'gatsby-plugin-sharp',
16+
{
17+
resolve: `gatsby-plugin-manifest`,
18+
options: {
19+
name: 'gatsby-starter-default',
20+
short_name: 'starter',
21+
start_url: '/',
22+
background_color: '#663399',
23+
theme_color: '#663399',
24+
display: 'minimal-ui',
25+
icon: 'src/images/gatsby-icon.png', // This path is relative to the root of the site.
26+
},
27+
},
28+
'gatsby-plugin-offline',
29+
],
30+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const babelOptions = {
2+
presets: ['babel-preset-gatsby'],
3+
}
4+
5+
module.exports = require('babel-jest').createTransformer(babelOptions)

examples/using-jest/jest.config.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
module.exports = {
2+
transform: {
3+
'^.+\\.jsx?$': '<rootDir>/jest-preprocess.js',
4+
},
5+
moduleNameMapper: {
6+
'.+\\.(css|styl|less|sass|scss)$': 'identity-obj-proxy',
7+
'.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
8+
'<rootDir>/__mocks__/fileMock.js',
9+
},
10+
testPathIgnorePatterns: ['node_modules', '.cache'],
11+
transformIgnorePatterns: ['node_modules/(?!(gatsby)/)'],
12+
globals: {
13+
__PATH_PREFIX__: '',
14+
},
15+
testURL: 'http://localhost',
16+
setupTestFrameworkScriptFile: '<rootDir>/jest.setup.js',
17+
setupFiles: ['<rootDir>/loadershim.js'],
18+
}

examples/using-jest/jest.setup.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import 'jest-dom/extend-expect'
2+
import 'react-testing-library/cleanup-after-each'

examples/using-jest/loadershim.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
global.___loader = {
2+
enqueue: jest.fn(),
3+
}

0 commit comments

Comments
 (0)