Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: ensure babel-preset-gatsby can be used with unit tests #9629

Merged
merged 12 commits into from
Nov 5, 2018
204 changes: 87 additions & 117 deletions docs/docs/unit-testing.md
Original file line number Diff line number Diff line change
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", () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is quite a bit clearer/real-world, even though both were valid

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
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
public
.cache
node_modules
5 changes: 5 additions & 0 deletions examples/using-jest/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"semi": false,
"singleQuote": true,
"trailingComma": "es5"
}
22 changes: 22 additions & 0 deletions examples/using-jest/LICENSE
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will this link work? Looks...incomplete to me

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh and it's mentioned below too, with the complete link

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah! These are markdown style links, so it's like a table of links that it refers to below. Actually a good practice to get into, I think :) Keeps links in one place so they're easy to edit/re-use!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as an example if it's helpful!

This is a link to [google][google], and [this link][google] will also refer to the google link! 🎉 

[google]: https://google.com

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ohhhhhh kewl

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that seems handy. I'll keep that in mind!


[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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = 'test-file-stub'
14 changes: 14 additions & 0 deletions examples/using-jest/__mocks__/gatsby.js
Original file line number Diff line number Diff line change
@@ -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 }) =>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kinda janky, but I also think that this should probably be added to the docs/unit-testing.md document!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(also just for clarity here, gatsby-link errors out in Jest, so this is a way to side step that issue)

React.createElement('a', {
...rest,
href: to,
})
),
StaticQuery: jest.fn(),
}
30 changes: 30 additions & 0 deletions examples/using-jest/gatsby-config.js
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
global.___loader = {
enqueue: jest.fn(),
}
Loading