Skip to content

Commit 3861509

Browse files
authored
docs: replace "axiosMock" with "msw" in React Testing Library example (#483)
1 parent 2570111 commit 3861509

File tree

1 file changed

+110
-56
lines changed

1 file changed

+110
-56
lines changed

docs/react-testing-library/example-intro.md

Lines changed: 110 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -11,32 +11,55 @@ See the following sections for a detailed breakdown of the test
1111
```jsx
1212
// __tests__/fetch.test.js
1313
import React from 'react'
14+
import { rest } from 'msw'
15+
import { setupServer } from 'msw/node'
1416
import { render, fireEvent, waitFor, screen } from '@testing-library/react'
1517
import '@testing-library/jest-dom/extend-expect'
16-
import axiosMock from 'axios'
1718
import Fetch from '../fetch'
1819

19-
jest.mock('axios')
20+
const server = setupServer(
21+
rest.get('/greeting', (req, res, ctx) => {
22+
return res(ctx.json({ greeting: 'hello there' }))
23+
})
24+
)
2025

21-
test('loads and displays greeting', async () => {
22-
const url = '/greeting'
23-
render(<Fetch url={url} />)
26+
beforeAll(() => server.listen())
27+
afterEach(() => server.resetHandlers())
28+
afterAll(() => server.close())
2429

25-
axiosMock.get.mockResolvedValueOnce({
26-
data: { greeting: 'hello there' },
27-
})
30+
test('loads and displays greeting', async () => {
31+
render(<Fetch url="/greeting" />)
2832

2933
fireEvent.click(screen.getByText('Load Greeting'))
3034

3135
await waitFor(() => screen.getByRole('heading'))
3236

33-
expect(axiosMock.get).toHaveBeenCalledTimes(1)
34-
expect(axiosMock.get).toHaveBeenCalledWith(url)
3537
expect(screen.getByRole('heading')).toHaveTextContent('hello there')
3638
expect(screen.getByRole('button')).toHaveAttribute('disabled')
3739
})
40+
41+
test('handlers server error', async () => {
42+
server.use(
43+
rest.get('/greeting', (req, res, ctx) => {
44+
return res(ctx.status(500))
45+
})
46+
)
47+
48+
render(<Fetch url="/greeting" />)
49+
50+
fireEvent.click(screen.getByText('Load Greeting'))
51+
52+
await waitFor(() => screen.getByRole('alert'))
53+
54+
expect(screen.getByRole('alert')).toHaveTextContent('Oops, failed to fetch!')
55+
expect(screen.getByRole('button')).not.toHaveAttribute('disabled')
56+
})
3857
```
3958

59+
> We recommend using [Mock Service Worker](https://github.com/mswjs/msw) library
60+
> to declaratively mock API communication in your tests instead of stubbing
61+
> `window.fetch`, or relying on third-party adapters.
62+
4063
---
4164

4265
## Step-By-Step
@@ -47,17 +70,17 @@ test('loads and displays greeting', async () => {
4770
// import dependencies
4871
import React from 'react'
4972

73+
// import API mocking utilities from Mock Service Worker
74+
import { rest } from 'msw'
75+
import { setupServer } from 'msw/node'
76+
5077
// import react-testing methods
5178
import { render, fireEvent, waitFor, screen } from '@testing-library/react'
5279

5380
// add custom jest matchers from jest-dom
5481
import '@testing-library/jest-dom/extend-expect'
55-
import axiosMock from 'axios'
5682
// the component to test
5783
import Fetch from '../fetch'
58-
59-
// https://jestjs.io/docs/en/mock-functions#mocking-modules
60-
jest.mock('axios')
6184
```
6285

6386
```jsx
@@ -68,13 +91,51 @@ test('loads and displays greeting', async () => {
6891
})
6992
```
7093

94+
### Mock
95+
96+
Use the `setupServer` function from `msw` to mock an API request that our tested
97+
component makes.
98+
99+
```js
100+
// declare which API requests to mock
101+
const server = setupServer(
102+
// capture "GET /greeting" requests
103+
rest.get('/greeting', (req, res, ctx) => {
104+
// respond using a mocked JSON body
105+
return res(ctx.json({ greeting: 'hello there' }))
106+
})
107+
)
108+
109+
// establish API mocking before all tests
110+
beforeAll(() => server.listen())
111+
// reset any request handlers that are declared as a part of our tests
112+
// (i.e. for testing one-time error scenarios)
113+
afterEach(() => server.resetHandlers())
114+
// clean up once the tests are done
115+
afterAll(() => server.close())
116+
117+
// ...
118+
119+
test('handlers server error', async () => {
120+
server.use(
121+
// override the initial "GET /greeting" request handler
122+
// to return a 500 Server Error
123+
rest.get('/greeting', (req, res, ctx) => {
124+
return res(ctx.status(500))
125+
})
126+
)
127+
128+
// ...
129+
})
130+
```
131+
71132
### Arrange
72133

73-
The [`render`](./api#render) method renders a React element into the DOM and returns utility functions for testing the component.
134+
The [`render`](./api#render) method renders a React element into the DOM and
135+
returns utility functions for testing the component.
74136

75137
```jsx
76-
const url = '/greeting'
77-
const { container, asFragment } = render(<Fetch url={url} />)
138+
const { container, asFragment } = render(<Fetch url="/greeting" />)
78139
```
79140

80141
### Act
@@ -83,13 +144,9 @@ The [`fireEvent`](dom-testing-library/api-events.md) method allows you to fire
83144
events to simulate user actions.
84145

85146
```jsx
86-
axiosMock.get.mockResolvedValueOnce({
87-
data: { greeting: 'hello there' },
88-
})
89-
90147
fireEvent.click(screen.getByText('Load Greeting'))
91148

92-
// Wait until the mocked `get` request promise resolves and
149+
// wait until the `get` request promise resolves and
93150
// the component calls setState and re-renders.
94151
// `waitFor` waits until the callback doesn't throw an error
95152

@@ -107,16 +164,39 @@ fetch.js
107164
import React, { useState } from 'react'
108165
import axios from 'axios'
109166

167+
function greetingReducer(state, action) {
168+
switch (action.type) {
169+
case 'SUCCESS': {
170+
return {
171+
error: null,
172+
greeting: action.greeting,
173+
}
174+
}
175+
case: 'ERROR': {
176+
error: action.error,
177+
greeting: null
178+
}
179+
default: {
180+
return state
181+
}
182+
}
183+
}
184+
110185
export default function Fetch({ url }) {
111-
const [greeting, setGreeting] = useState('')
186+
const [{ error, greeting }, dispatch] = useReducer(greetingReducer)
112187
const [buttonClicked, setButtonClicked] = useState(false)
113188

114189
const fetchGreeting = async () => {
115-
const response = await axios.get(url)
116-
const data = response.data
117-
const { greeting } = data
118-
setGreeting(greeting)
119-
setButtonClicked(true)
190+
axios.get(url)
191+
.then((response) => {
192+
const { data } = response
193+
const { greeting } = data
194+
dispatch({ type: 'SUCCESS', greeting })
195+
setButtonClicked(true)
196+
})
197+
.catch((error) => {
198+
dispatch({ type: 'ERROR' })
199+
})
120200
}
121201

122202
const buttonText = buttonClicked ? 'Ok' : 'Load Greeting'
@@ -126,35 +206,9 @@ export default function Fetch({ url }) {
126206
<button onClick={fetchGreeting} disabled={buttonClicked}>
127207
{buttonText}
128208
</button>
129-
{greeting ? <h1>{greeting}</h1> : null}
209+
{greeting && <h1>{greeting}</h1>}
210+
{error && <p role="alert">Oops, failed to fetch!</p>}
130211
</div>
131212
)
132213
}
133214
```
134-
135-
```jsx
136-
expect(axiosMock.get).toHaveBeenCalledTimes(1)
137-
expect(axiosMock.get).toHaveBeenCalledWith(url)
138-
expect(screen.getByRole('heading')).toHaveTextContent('hello there')
139-
expect(screen.getByRole('button')).toHaveAttribute('disabled')
140-
141-
// snapshots work great with regular DOM nodes!
142-
expect(container).toMatchInlineSnapshot(`
143-
<div>
144-
<div>
145-
<button
146-
disabled=""
147-
>
148-
Ok
149-
</button>
150-
<h1>
151-
hello there
152-
</h1>
153-
</div>
154-
</div>
155-
`)
156-
157-
// you can also use get a `DocumentFragment`,
158-
// which is useful if you want to compare nodes across render
159-
expect(asFragment()).toMatchSnapshot()
160-
```

0 commit comments

Comments
 (0)