-
Notifications
You must be signed in to change notification settings - Fork 10.3k
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
[docs] Add unit testing guide #6678
Merged
Merged
Changes from 15 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
522def7
Add unit testing guide
0569761
Typo fixes
aa21ceb
Proofreading
5f7371c
Add info on testing gatsby-link
162d220
Updates for style
a9ccfff
Switch yarn to npm
3b1c69e
Add babel presets to npm install
8b81108
Small edits
m-allanson 450aa46
Small edits from review
53fdc5b
Merge branch 'unit-testing-docs' of github.com:ascorbic/gatsby into u…
f24fd7e
Add loader shim and __PATH_PREFIX__ global
1035740
Add testing graphql doc
71a2f30
Rename
e4fcbb3
Mock Gatsby rather than removing graphql and using MemoryRouter
64fd755
Add filename for gatsby mock
1a0da43
Update LInk mock so all props can be displayed
b3dd198
Details on why you might not want to mock Link
17b86d2
Add not on use of StaticQuery, and a little on component purity
f9d6aa2
Update for @reach/router
01898bb
Add testURL to Jest config
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,321 @@ | ||
--- | ||
Title: Testing components with GraphQL | ||
--- | ||
|
||
If you try to run unit tests on components that use GraphQL queries, you will | ||
discover that you have no data. Jest can't run your queries, so if you are | ||
testing components that rely on GraphQL data, you will need to provide the data | ||
yourself. This is a good thing, as otherwise your tests could break if your data | ||
changes, and in the case of remote data sources it would need network access to | ||
run tests. | ||
|
||
In general it is best practice to test the smallest components possible, so the | ||
simplest thing to do is to test the individual page components with mock data, | ||
rather than trying to test a full page. However, if you do want to test the full | ||
page you'll need to provide the equivalent data to the component. Luckily | ||
there's a simple way to get the data you need. | ||
|
||
First you should make sure you have read | ||
[the unit testing guide](/docs/unit-testing/) and set up your project as | ||
described. This guide is based on the same blog starter project. You will be | ||
writing a simple snapshot test for the index page. | ||
|
||
As Jest doesn't run or compile away your GraphQL queries you need to mock the | ||
`graphql` function to stop it throwing an error. If you set your project up with | ||
a mock for `gatsby` as described in the unit testing guide then this is already | ||
done. | ||
|
||
## Testing page queries | ||
|
||
As this is testing a page component you will need to put your tests in another | ||
folder so that Gatsby doesn't try to turn the tests into pages. | ||
|
||
```js | ||
// 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 tree = renderer.create(<BlogIndex />).toJSON() | ||
expect(tree).toMatchSnapshot() | ||
})) | ||
``` | ||
|
||
If you run this test you will get an error, as the component is expecting a | ||
location object. You can fix this by passing one in: | ||
|
||
```js | ||
// 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 should fix the `location` error, but now you will have an error because | ||
there is no GraphQL data being passed to the component. We can pass this in too, | ||
but the structure is a little more complicated. Luckily there's an easy way to | ||
get some suitable data. Run `gatsby develop` and go to | ||
http://localhost:8000/___graphql to load the GraphiQL IDE. You can now get the | ||
right data using the same query that you used on the page. If it is a simple | ||
query with no fragments you can copy it directly. That is the case here, run | ||
this query copied from the index page: | ||
|
||
```graphql | ||
query IndexQuery { | ||
site { | ||
siteMetadata { | ||
title | ||
} | ||
} | ||
allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) { | ||
edges { | ||
node { | ||
excerpt | ||
fields { | ||
slug | ||
} | ||
frontmatter { | ||
date(formatString: "DD MMMM, YYYY") | ||
title | ||
} | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
The output panel should now give you a nice JSON object with the query result. | ||
Here it is, trimmed to one node for brevity: | ||
|
||
```json | ||
{ | ||
"data": { | ||
"site": { | ||
"siteMetadata": { | ||
"title": "Gatsby Starter Blog" | ||
} | ||
}, | ||
"allMarkdownRemark": { | ||
"edges": [ | ||
{ | ||
"node": { | ||
"excerpt": | ||
"Far far away, behind the word mountains, far from the countries Vokalia and\nConsonantia, there live the blind texts. Separated they live in…", | ||
"fields": { | ||
"slug": "/hi-folks/" | ||
}, | ||
"frontmatter": { | ||
"date": "28 May, 2015", | ||
"title": "New Beginnings" | ||
} | ||
} | ||
} | ||
] | ||
} | ||
} | ||
} | ||
``` | ||
|
||
GraphiQL doesn't know about any fragments defined by Gatsby, so if your query | ||
uses them then you'll need to replace those with the content of the fragment. If | ||
you're using `gatsby-transformer-sharp` you'll find the fragments in | ||
[gatsby-transformer-sharp/src/fragments.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-transformer-sharp/src/fragments.js). | ||
So, for example if your query includes: | ||
|
||
```graphql | ||
image { | ||
childImageSharp { | ||
fluid(maxWidth: 1024) { | ||
...GatsbyImageSharpFluid | ||
} | ||
} | ||
} | ||
``` | ||
|
||
...it becomes: | ||
|
||
```graphql | ||
image { | ||
childImageSharp { | ||
fluid(maxWidth: 1024) { | ||
base64 | ||
aspectRatio | ||
src | ||
srcSet | ||
sizes | ||
} | ||
} | ||
} | ||
``` | ||
|
||
When you have the result, copy the `data` value from the output panel. Good | ||
practice is to store your fixtures in a separate file, but for simplicity here | ||
you will be defining it directly inside your test file: | ||
|
||
```js | ||
// 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 data = { | ||
site: { | ||
siteMetadata: { | ||
title: "Gatsby Starter Blog", | ||
}, | ||
}, | ||
allMarkdownRemark: { | ||
edges: [ | ||
{ | ||
node: { | ||
excerpt: | ||
"Far far away, behind the word mountains, far from the countries Vokalia and\nConsonantia, there live the blind texts. Separated they live in…", | ||
fields: { | ||
slug: "/hi-folks/", | ||
}, | ||
frontmatter: { | ||
date: "28 May, 2015", | ||
title: "New Beginnings", | ||
}, | ||
}, | ||
}, | ||
], | ||
}, | ||
} | ||
|
||
const tree = renderer | ||
.create(<BlogIndex location={location} data={data} />) | ||
.toJSON() | ||
expect(tree).toMatchSnapshot() | ||
})) | ||
``` | ||
|
||
Run the tests and they should now pass. Take a look in `__snapshots__` to see | ||
the output. | ||
|
||
## Testing StaticQuery | ||
|
||
The method above works for page queries, as you can pass the data in directly to | ||
the component. This doesn't work for components that use `StaticQuery` though, | ||
so we need to take a slightly different approach to testing these. The blog | ||
starter project doesn't include `StaticQuery`, so the example here is from | ||
[the StaticQuery docs](/docs/static-query/). | ||
|
||
The pattern for enabling type checking described in the docs is a good starting | ||
point for making these components testable, but you need access to the | ||
component. | ||
|
||
Here is the example: | ||
|
||
```js | ||
import React from "react" | ||
import { StaticQuery } from "gatsby" | ||
|
||
const Header = ({ data }) => ( | ||
<header> | ||
<h1>{data.site.siteMetadata.title}</h1> | ||
</header> | ||
) | ||
|
||
export default props => ( | ||
<StaticQuery | ||
query={graphql` | ||
query { | ||
site { | ||
siteMetadata { | ||
title | ||
} | ||
} | ||
} | ||
`} | ||
render={data => <Header data={data} {...props} />} | ||
/> | ||
) | ||
``` | ||
|
||
This is almost ready: all you need to do is export the pure component that you | ||
are passing to StaticQuery. Rename it first to avoid confusion: | ||
|
||
```js | ||
// src/components/Header.js | ||
import React from "react" | ||
import { StaticQuery, graphql } from "gatsby" | ||
|
||
export const PureHeader = ({ data }) => ( | ||
<header> | ||
<h1>{data.site.siteMetadata.title}</h1> | ||
</header> | ||
) | ||
|
||
export const Header = props => ( | ||
<StaticQuery | ||
query={graphql` | ||
query { | ||
site { | ||
siteMetadata { | ||
title | ||
} | ||
} | ||
} | ||
`} | ||
render={data => <PureHeader data={data} {...props} />} | ||
/> | ||
) | ||
|
||
export default Header | ||
``` | ||
|
||
Now you have two components exported from the file: the component that includes | ||
the StaticQuery data which is still the default export, and a pure component | ||
that you can test. Here's how: | ||
|
||
```js | ||
// src/components/Header.test.js | ||
|
||
import React from "react" | ||
import renderer from "react-test-renderer" | ||
import { PureHeader as Header } from "./Header" | ||
|
||
describe("Header", () => | ||
it("renders correctly", () => { | ||
// Created using the query from Header.js | ||
const data = { | ||
site: { | ||
siteMetadata: { | ||
title: "Gatsby Starter Blog", | ||
}, | ||
}, | ||
} | ||
const tree = renderer.create(<Header data={data} />).toJSON() | ||
expect(tree).toMatchSnapshot() | ||
})) | ||
``` | ||
|
||
This means you can test the component independently of the GraphQL. | ||
|
||
## Using TypeScript | ||
|
||
If you are using TypeScript this is a lot easier to get right as the type errors | ||
will tell you exaclty what you should be passing to the components. This is why | ||
it is a good idea to define type interfaces for all of your GraphQL queries. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What kind of file should someone use this code example for? Maybe readers will just know, but wanted to make sure if it's supposed to go in a specific type of file or you have a suggestion, make it clear here as you did for other code examples!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As in where they'd be using StaticQuery rather than a page query? No problem.