Skip to content

Commit 16bf528

Browse files
authored
[docs] Add unit testing guide (#6678) (#7386)
* Add unit testing guide * Typo fixes * Proofreading * Add info on testing gatsby-link * Updates for style * Switch yarn to npm * Add babel presets to npm install * Small edits * Small edits from review * Add loader shim and __PATH_PREFIX__ global * Add testing graphql doc * Rename * Mock Gatsby rather than removing graphql and using MemoryRouter * Add filename for gatsby mock * Update LInk mock so all props can be displayed * Details on why you might not want to mock Link * Add not on use of StaticQuery, and a little on component purity * Update for @reach/router * Add testURL to Jest config Because of jestjs/jest#6766
1 parent 4e4946c commit 16bf528

File tree

2 files changed

+665
-0
lines changed

2 files changed

+665
-0
lines changed
Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
---
2+
Title: Testing components with GraphQL
3+
---
4+
5+
If you try to run unit tests on components that use GraphQL queries, you will
6+
discover that you have no data. Jest can't run your queries, so if you are
7+
testing components that rely on GraphQL data, you will need to provide the data
8+
yourself. This is a good thing, as otherwise your tests could break if your data
9+
changes, and in the case of remote data sources it would need network access to
10+
run tests.
11+
12+
In general it is best practice to test the smallest components possible, so the
13+
simplest thing to do is to test the individual page components with mock data,
14+
rather than trying to test a full page. However, if you do want to test the full
15+
page you'll need to provide the equivalent data to the component. Luckily
16+
there's a simple way to get the data you need.
17+
18+
First you should make sure you have read
19+
[the unit testing guide](/docs/unit-testing/) and set up your project as
20+
described. This guide is based on the same blog starter project. You will be
21+
writing a simple snapshot test for the index page.
22+
23+
As Jest doesn't run or compile away your GraphQL queries you need to mock the
24+
`graphql` function to stop it throwing an error. If you set your project up with
25+
a mock for `gatsby` as described in the unit testing guide then this is already
26+
done.
27+
28+
## Testing page queries
29+
30+
As this is testing a page component you will need to put your tests in another
31+
folder so that Gatsby doesn't try to turn the tests into pages.
32+
33+
```js
34+
// src/__tests__/index.js
35+
36+
import React from "react"
37+
import renderer from "react-test-renderer"
38+
import BlogIndex from "../pages/index"
39+
40+
describe("BlogIndex", () =>
41+
it("renders correctly", () => {
42+
const tree = renderer.create(<BlogIndex />).toJSON()
43+
expect(tree).toMatchSnapshot()
44+
}))
45+
```
46+
47+
If you run this test you will get an error, as the component is expecting a
48+
location object. You can fix this by passing one in:
49+
50+
```js
51+
// src/__tests__/index.js
52+
53+
import React from "react"
54+
import renderer from "react-test-renderer"
55+
import BlogIndex from "../pages/index"
56+
57+
describe("BlogIndex", () =>
58+
it("renders correctly", () => {
59+
const location = {
60+
pathname: "",
61+
}
62+
63+
const tree = renderer.create(<BlogIndex location={location} />).toJSON()
64+
expect(tree).toMatchSnapshot()
65+
}))
66+
```
67+
68+
This should fix the `location` error, but now you will have an error because
69+
there is no GraphQL data being passed to the component. We can pass this in too,
70+
but the structure is a little more complicated. Luckily there's an easy way to
71+
get some suitable data. Run `gatsby develop` and go to
72+
http://localhost:8000/___graphql to load the GraphiQL IDE. You can now get the
73+
right data using the same query that you used on the page. If it is a simple
74+
query with no fragments you can copy it directly. That is the case here, run
75+
this query copied from the index page:
76+
77+
```graphql
78+
query IndexQuery {
79+
site {
80+
siteMetadata {
81+
title
82+
}
83+
}
84+
allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) {
85+
edges {
86+
node {
87+
excerpt
88+
fields {
89+
slug
90+
}
91+
frontmatter {
92+
date(formatString: "DD MMMM, YYYY")
93+
title
94+
}
95+
}
96+
}
97+
}
98+
}
99+
```
100+
101+
The output panel should now give you a nice JSON object with the query result.
102+
Here it is, trimmed to one node for brevity:
103+
104+
```json
105+
{
106+
"data": {
107+
"site": {
108+
"siteMetadata": {
109+
"title": "Gatsby Starter Blog"
110+
}
111+
},
112+
"allMarkdownRemark": {
113+
"edges": [
114+
{
115+
"node": {
116+
"excerpt":
117+
"Far far away, behind the word mountains, far from the countries Vokalia and\nConsonantia, there live the blind texts. Separated they live in…",
118+
"fields": {
119+
"slug": "/hi-folks/"
120+
},
121+
"frontmatter": {
122+
"date": "28 May, 2015",
123+
"title": "New Beginnings"
124+
}
125+
}
126+
}
127+
]
128+
}
129+
}
130+
}
131+
```
132+
133+
GraphiQL doesn't know about any fragments defined by Gatsby, so if your query
134+
uses them then you'll need to replace those with the content of the fragment. If
135+
you're using `gatsby-transformer-sharp` you'll find the fragments in
136+
[gatsby-transformer-sharp/src/fragments.js](https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-transformer-sharp/src/fragments.js).
137+
So, for example if your query includes:
138+
139+
```graphql
140+
image {
141+
childImageSharp {
142+
fluid(maxWidth: 1024) {
143+
...GatsbyImageSharpFluid
144+
}
145+
}
146+
}
147+
```
148+
149+
...it becomes:
150+
151+
```graphql
152+
image {
153+
childImageSharp {
154+
fluid(maxWidth: 1024) {
155+
base64
156+
aspectRatio
157+
src
158+
srcSet
159+
sizes
160+
}
161+
}
162+
}
163+
```
164+
165+
When you have the result, copy the `data` value from the output panel. Good
166+
practice is to store your fixtures in a separate file, but for simplicity here
167+
you will be defining it directly inside your test file:
168+
169+
```js
170+
// src/__tests__/index.js
171+
172+
import React from "react"
173+
import renderer from "react-test-renderer"
174+
import BlogIndex from "../pages/index"
175+
176+
describe("BlogIndex", () =>
177+
it("renders correctly", () => {
178+
const location = {
179+
pathname: "",
180+
}
181+
182+
const data = {
183+
site: {
184+
siteMetadata: {
185+
title: "Gatsby Starter Blog",
186+
},
187+
},
188+
allMarkdownRemark: {
189+
edges: [
190+
{
191+
node: {
192+
excerpt:
193+
"Far far away, behind the word mountains, far from the countries Vokalia and\nConsonantia, there live the blind texts. Separated they live in…",
194+
fields: {
195+
slug: "/hi-folks/",
196+
},
197+
frontmatter: {
198+
date: "28 May, 2015",
199+
title: "New Beginnings",
200+
},
201+
},
202+
},
203+
],
204+
},
205+
}
206+
207+
const tree = renderer
208+
.create(<BlogIndex location={location} data={data} />)
209+
.toJSON()
210+
expect(tree).toMatchSnapshot()
211+
}))
212+
```
213+
214+
Run the tests and they should now pass. Take a look in `__snapshots__` to see
215+
the output.
216+
217+
## Testing StaticQuery
218+
219+
The method above works for page queries, as you can pass the data in directly to
220+
the component. This doesn't work for components that use `StaticQuery` though,
221+
as that uses `context` rather than `props` so we need to take a slightly
222+
different approach to testing these. The blog starter project doesn't include
223+
`StaticQuery`, so the example here is from
224+
[the StaticQuery docs](/docs/static-query/).
225+
226+
Using `StaticQuery` allows you to make queries in any component, not just pages.
227+
This gives a lot of flexibility, and avoid having to pass the props down to
228+
deeply-nested components. The pattern for enabling type checking described in
229+
the docs is a good starting point for making these components testable, as it
230+
separates the query from the definition of the component itself. However that
231+
example doesn't export the inner, pure component, which is what you'll need to
232+
test.
233+
234+
Here is the example of a header component that queries the page data itself,
235+
rather than needing it to be passed from the layout:
236+
237+
```js
238+
// src/components/Header.js
239+
import React from "react"
240+
import { StaticQuery } from "gatsby"
241+
242+
const Header = ({ data }) => (
243+
<header>
244+
<h1>{data.site.siteMetadata.title}</h1>
245+
</header>
246+
)
247+
248+
export default props => (
249+
<StaticQuery
250+
query={graphql`
251+
query {
252+
site {
253+
siteMetadata {
254+
title
255+
}
256+
}
257+
}
258+
`}
259+
render={data => <Header data={data} {...props} />}
260+
/>
261+
)
262+
```
263+
264+
This is almost ready: all you need to do is export the pure component that you
265+
are passing to StaticQuery. Rename it first to avoid confusion:
266+
267+
```js
268+
// src/components/Header.js
269+
import React from "react"
270+
import { StaticQuery, graphql } from "gatsby"
271+
272+
export const PureHeader = ({ data }) => (
273+
<header>
274+
<h1>{data.site.siteMetadata.title}</h1>
275+
</header>
276+
)
277+
278+
export const Header = props => (
279+
<StaticQuery
280+
query={graphql`
281+
query {
282+
site {
283+
siteMetadata {
284+
title
285+
}
286+
}
287+
}
288+
`}
289+
render={data => <PureHeader data={data} {...props} />}
290+
/>
291+
)
292+
293+
export default Header
294+
```
295+
296+
Now you have two components exported from the file: the component that includes
297+
the StaticQuery data which is still the default export, and another component
298+
that you can test. This means you can test the component independently of the
299+
GraphQL.
300+
301+
This is a good example of the benefits of keeping components "pure", meaning
302+
they always generate the same output if given the same inputs and have no
303+
side-effects apart from their return value. This means we can be sure the tests
304+
are always reproducable and don't fail if, for example, the network is down or
305+
the data source changes. In this example, `Header` is impure as it makes a
306+
query, so the output depends on something apart from its props. `PureHeader` is
307+
pure because its return value is entirely dependent on the props passed it it.
308+
This means it's very easy to test, and a snapshot should never change.
309+
310+
Here's how:
311+
312+
```js
313+
// src/components/Header.test.js
314+
315+
import React from "react"
316+
import renderer from "react-test-renderer"
317+
import { PureHeader as Header } from "./Header"
318+
319+
describe("Header", () =>
320+
it("renders correctly", () => {
321+
// Created using the query from Header.js
322+
const data = {
323+
site: {
324+
siteMetadata: {
325+
title: "Gatsby Starter Blog",
326+
},
327+
},
328+
}
329+
const tree = renderer.create(<Header data={data} />).toJSON()
330+
expect(tree).toMatchSnapshot()
331+
}))
332+
```
333+
334+
## Using TypeScript
335+
336+
If you are using TypeScript this is a lot easier to get right as the type errors
337+
will tell you exaclty what you should be passing to the components. This is why
338+
it is a good idea to define type interfaces for all of your GraphQL queries.

0 commit comments

Comments
 (0)