Skip to content

Commit

Permalink
fix: ensure babel-preset-gatsby can be used with unit tests (#9629)
Browse files Browse the repository at this point in the history
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/
-->
  • Loading branch information
DSchau authored and pieh committed Nov 5, 2018
1 parent bbf1f15 commit 401df07
Show file tree
Hide file tree
Showing 30 changed files with 1,240 additions and 121 deletions.
204 changes: 87 additions & 117 deletions docs/docs/unit-testing.md
Expand Up @@ -26,32 +26,25 @@ npm install --save-dev jest babel-jest react-test-renderer identity-obj-proxy 'b
```

Because Gatsby handles its own Babel configuration, you will need to manually
tell Jest to use `babel-jest`. The easiest way to do this is to add a `"jest"`
section in your `package.json`. You can set up some useful defaults at the same
time:

```json:title=package.json
"jest": {
"transform": {
"^.+\\.jsx?$": "<rootDir>/jest-preprocess.js"
},
"testRegex": "/.*(__tests__\\/.*)|(.*(test|spec))\\.jsx?$",
"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__/fileMock.js"
},
"testPathIgnorePatterns": ["node_modules", ".cache"],
"transformIgnorePatterns": [
"node_modules/(?!(gatsby)/)"
],
"globals": {
"__PATH_PREFIX__": ""
},
"testURL": "http://localhost",
"setupFiles": [
"<rootDir>/loadershim.js"
]
}
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:

```json:title=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__/fileMock.js"
},
"testPathIgnorePatterns": ["node_modules", ".cache"],
"transformIgnorePatterns": ["node_modules/(?!(gatsby)/)"],
"globals": {
"__PATH_PREFIX__": ""
},
"testURL": "http://localhost",
"setupFiles": ["<rootDir>/loadershim.js"]
}
```

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

```js:title=__mocks__/gatsby.js
const React = require("react")
const gatsby = jest.requireActual("gatsby")
module.exports = { ...gatsby, graphql: jest.fn(), Link: "Link" }

module.exports = {
...gatsby,
graphql: jest.fn(),
Link: jest.fn().mockImplementation(({ to, ...rest }) =>
React.createElement("a", {
...rest,
href: to,
})
),
StaticQuery: jest.fn(),
}
```

This mocks the `graphql()` function, `Link` component, and `StaticQuery` component.

One more issue that you may encounter is that some components expect to be able
to use the `location` prop that is passed in by `Router`. You can fix this by
manually passing in the prop:

```js:title=src/__tests__/index.js
import React from "react"
import renderer from "react-test-renderer"
import BlogIndex from "../pages/index"

describe("BlogIndex", () => {
it("renders correctly", () => {
const location = {
pathname: "/",
}

const tree = renderer.create(<BlogIndex location={location} />).toJSON()
expect(tree).toMatchSnapshot()
}))
})
```

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

## Writing tests

Expand All @@ -150,11 +179,12 @@ import React from "react"
import renderer from "react-test-renderer"
import Bio from "./Bio"

describe("Bio", () =>
describe("Bio", () => {
it("renders correctly", () => {
const tree = renderer.create(<Bio />).toJSON()
expect(tree).toMatchSnapshot()
}))
})
})
```

This is a very simple snapshot test, which uses `react-test-renderer` to render
Expand Down Expand Up @@ -220,95 +250,30 @@ config. First install `ts-jest`:
npm install --save-dev ts-jest
```
Then edit the Jest config in your `package.json` to match this:
```json:title=package.json
"jest": {
"transform": {
"^.+\\.tsx?$": "ts-jest",
"^.+\\.jsx?$": "<rootDir>/jest-preprocess.js"
},
"testRegex": "(/__tests__/.*\\.([tj]sx?)|(\\.|/)(test|spec))\\.([tj]sx?)$",
"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__/fileMock.js"
},
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
],
"testPathIgnorePatterns": ["node_modules", ".cache"],
"transformIgnorePatterns": [
"node_modules/(?!(gatsby)/)"
],
"globals": {
"__PATH_PREFIX__": ""
},
"testURL": "http://localhost",
"setupFiles": [
"<rootDir>/loadershim.js"
]
}
```
## Testing components with Router
When you test components they are not in a `Router`, meaning they don't have
access to some context and props that they may be expecting. The most common of
these is the `Link` component. In the example above we mock the `Link` component
as a string, which is the simplest solution and works for most uses. However
sometimes you might want to test with the real `Link` component. As of v2,
Gatsby uses `@reach/router` for navigation, which is good at handling test
environments, and unlike React Router is happy to render `Link`s outside of a
`Router` context. However there is a small issue related to the `gatsby` mock.
We can use a small workaround to avoid an error.
First, remove the `Link` mock from `gatsby`:
```js:title=__mocks__/gatsby.js
const gatsby = jest.requireActual("gatsby")
module.exports = { ...gatsby, graphql: jest.fn() }
```
While the `Link` component is exported by the main `gatsby` package, it is
actually defined in `gatsby-link`. That in turn uses `parsePath()` from
`gatsby`, which causes module resolution issues. Fortunately it's an easy fix.
You need to create a mock for `gatsby-link`, even though it will actually be the
real module. You do this so that you can tell it to not try and use the mock
`gatsby`:
```js:title=__mocks__/gatsby-link.js
jest.unmock("gatsby")
module.exports = jest.requireActual("gatsby-link")
```
One more issue that you may encounter is that some components expect to be able
to use the `location` prop that is passed in by `Router`. You can fix this by
manually passing in the prop:
```js:title=src/__tests__/index.js
import React from "react"
import renderer from "react-test-renderer"
import BlogIndex from "../pages/index"
describe("BlogIndex", () =>
it("renders correctly", () => {
const location = {
pathname: "/",
}
const tree = renderer.create(<BlogIndex location={location} />).toJSON()
expect(tree).toMatchSnapshot()
}))
Then update the configuration in `jest.config.js`, like so:
```json:title=jest.config.js
module.exports = {
"transform": {
"^.+\\.tsx?$": "ts-jest",
"^.+\\.jsx?$": "<rootDir>/jest-preprocess.js"
},
"testRegex": "(/__tests__/.*\\.([tj]sx?)|(\\.|/)(test|spec))\\.([tj]sx?)$",
"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__/fileMock.js"
},
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"],
"testPathIgnorePatterns": ["node_modules", ".cache"],
"transformIgnorePatterns": ["node_modules/(?!(gatsby)/)"],
"globals": {
"__PATH_PREFIX__": ""
},
"testURL": "http://localhost",
"setupFiles": ["<rootDir>/loadershim.js"]
}
```
For more information on testing page components, be sure to read the docs on
[testing components with GraphQL](/docs/testing-components-with-graphql/)
## Other resources
If you need to make changes to your Babel config, you can edit the config in
Expand All @@ -318,3 +283,8 @@ though remember you may need to install the Babel 7 versions. See
For more information on Jest testing, visit
[the Jest site](https://jestjs.io/docs/en/getting-started).
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.
[using-jest]: https://github.com/gatsbyjs/gatsby/tree/master/examples/using-jest
[react-testing-library]: https://github.com/kentcdodds/react-testing-library
3 changes: 3 additions & 0 deletions examples/using-jest/.gitignore
@@ -0,0 +1,3 @@
public
.cache
node_modules
5 changes: 5 additions & 0 deletions examples/using-jest/.prettierrc
@@ -0,0 +1,5 @@
{
"semi": false,
"singleQuote": true,
"trailingComma": "es5"
}
22 changes: 22 additions & 0 deletions examples/using-jest/LICENSE
@@ -0,0 +1,22 @@
The MIT License (MIT)

Copyright (c) 2015 gatsbyjs

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

17 changes: 17 additions & 0 deletions examples/using-jest/README.md
@@ -0,0 +1,17 @@
<p align="center">
<a href="https://www.gatsbyjs.org">
<img alt="Gatsby" src="https://www.gatsbyjs.org/monogram.svg" width="60" />
</a>
</p>
<h1 align="center">
using Jest
</h1>

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] 💪

Check out the [unit testing doc][unit-testing-doc] for further info!

[jest]: https://jestjs.io/
[react-testing-library]: https://github.com/kentcdodds/react-testing-library
[gatsby]: https://gatsbyjs.org
[unit-testing-doc]: https://www.gatsbyjs.org/docs/unit-testing/
1 change: 1 addition & 0 deletions examples/using-jest/__mocks__/fileMock.js
@@ -0,0 +1 @@
module.exports = 'test-file-stub'
14 changes: 14 additions & 0 deletions examples/using-jest/__mocks__/gatsby.js
@@ -0,0 +1,14 @@
const React = require('react')
const gatsby = jest.requireActual('gatsby')

module.exports = {
...gatsby,
graphql: jest.fn(),
Link: jest.fn().mockImplementation(({ to, ...rest }) =>
React.createElement('a', {
...rest,
href: to,
})
),
StaticQuery: jest.fn(),
}
30 changes: 30 additions & 0 deletions examples/using-jest/gatsby-config.js
@@ -0,0 +1,30 @@
module.exports = {
siteMetadata: {
title: 'Gatsby Default Starter',
},
plugins: [
'gatsby-plugin-react-helmet',
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
'gatsby-transformer-sharp',
'gatsby-plugin-sharp',
{
resolve: `gatsby-plugin-manifest`,
options: {
name: 'gatsby-starter-default',
short_name: 'starter',
start_url: '/',
background_color: '#663399',
theme_color: '#663399',
display: 'minimal-ui',
icon: 'src/images/gatsby-icon.png', // This path is relative to the root of the site.
},
},
'gatsby-plugin-offline',
],
}
5 changes: 5 additions & 0 deletions examples/using-jest/jest-preprocess.js
@@ -0,0 +1,5 @@
const babelOptions = {
presets: ['babel-preset-gatsby'],
}

module.exports = require('babel-jest').createTransformer(babelOptions)
18 changes: 18 additions & 0 deletions examples/using-jest/jest.config.js
@@ -0,0 +1,18 @@
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__/fileMock.js',
},
testPathIgnorePatterns: ['node_modules', '.cache'],
transformIgnorePatterns: ['node_modules/(?!(gatsby)/)'],
globals: {
__PATH_PREFIX__: '',
},
testURL: 'http://localhost',
setupTestFrameworkScriptFile: '<rootDir>/jest.setup.js',
setupFiles: ['<rootDir>/loadershim.js'],
}
2 changes: 2 additions & 0 deletions examples/using-jest/jest.setup.js
@@ -0,0 +1,2 @@
import 'jest-dom/extend-expect'
import 'react-testing-library/cleanup-after-each'
3 changes: 3 additions & 0 deletions examples/using-jest/loadershim.js
@@ -0,0 +1,3 @@
global.___loader = {
enqueue: jest.fn(),
}

0 comments on commit 401df07

Please sign in to comment.