diff --git a/docs/react-testing-library/example-intro.md b/docs/react-testing-library/example-intro.md index fe800acd9..a7753cbde 100644 --- a/docs/react-testing-library/example-intro.md +++ b/docs/react-testing-library/example-intro.md @@ -11,32 +11,55 @@ See the following sections for a detailed breakdown of the test ```jsx // __tests__/fetch.test.js import React from 'react' +import { rest } from 'msw' +import { setupServer } from 'msw/node' import { render, fireEvent, waitFor, screen } from '@testing-library/react' import '@testing-library/jest-dom/extend-expect' -import axiosMock from 'axios' import Fetch from '../fetch' -jest.mock('axios') +const server = setupServer( + rest.get('/greeting', (req, res, ctx) => { + return res(ctx.json({ greeting: 'hello there' })) + }) +) -test('loads and displays greeting', async () => { - const url = '/greeting' - render() +beforeAll(() => server.listen()) +afterEach(() => server.resetHandlers()) +afterAll(() => server.close()) - axiosMock.get.mockResolvedValueOnce({ - data: { greeting: 'hello there' }, - }) +test('loads and displays greeting', async () => { + render() fireEvent.click(screen.getByText('Load Greeting')) await waitFor(() => screen.getByRole('heading')) - expect(axiosMock.get).toHaveBeenCalledTimes(1) - expect(axiosMock.get).toHaveBeenCalledWith(url) expect(screen.getByRole('heading')).toHaveTextContent('hello there') expect(screen.getByRole('button')).toHaveAttribute('disabled') }) + +test('handlers server error', async () => { + server.use( + rest.get('/greeting', (req, res, ctx) => { + return res(ctx.status(500)) + }) + ) + + render() + + fireEvent.click(screen.getByText('Load Greeting')) + + await waitFor(() => screen.getByRole('alert')) + + expect(screen.getByRole('alert')).toHaveTextContent('Oops, failed to fetch!') + expect(screen.getByRole('button')).not.toHaveAttribute('disabled') +}) ``` +> We recommend using [Mock Service Worker](https://github.com/mswjs/msw) library +> to declaratively mock API communication in your tests instead of stubbing +> `window.fetch`, or relying on third-party adapters. + --- ## Step-By-Step @@ -47,17 +70,17 @@ test('loads and displays greeting', async () => { // import dependencies import React from 'react' +// import API mocking utilities from Mock Service Worker +import { rest } from 'msw' +import { setupServer } from 'msw/node' + // import react-testing methods import { render, fireEvent, waitFor, screen } from '@testing-library/react' // add custom jest matchers from jest-dom import '@testing-library/jest-dom/extend-expect' -import axiosMock from 'axios' // the component to test import Fetch from '../fetch' - -// https://jestjs.io/docs/en/mock-functions#mocking-modules -jest.mock('axios') ``` ```jsx @@ -68,13 +91,51 @@ test('loads and displays greeting', async () => { }) ``` +### Mock + +Use the `setupServer` function from `msw` to mock an API request that our tested +component makes. + +```js +// declare which API requests to mock +const server = setupServer( + // capture "GET /greeting" requests + rest.get('/greeting', (req, res, ctx) => { + // respond using a mocked JSON body + return res(ctx.json({ greeting: 'hello there' })) + }) +) + +// establish API mocking before all tests +beforeAll(() => server.listen()) +// reset any request handlers that are declared as a part of our tests +// (i.e. for testing one-time error scenarios) +afterEach(() => server.resetHandlers()) +// clean up once the tests are done +afterAll(() => server.close()) + +// ... + +test('handlers server error', async () => { + server.use( + // override the initial "GET /greeting" request handler + // to return a 500 Server Error + rest.get('/greeting', (req, res, ctx) => { + return res(ctx.status(500)) + }) + ) + + // ... +}) +``` + ### Arrange -The [`render`](./api#render) method renders a React element into the DOM and returns utility functions for testing the component. +The [`render`](./api#render) method renders a React element into the DOM and +returns utility functions for testing the component. ```jsx -const url = '/greeting' -const { container, asFragment } = render() +const { container, asFragment } = render() ``` ### Act @@ -83,13 +144,9 @@ The [`fireEvent`](dom-testing-library/api-events.md) method allows you to fire events to simulate user actions. ```jsx -axiosMock.get.mockResolvedValueOnce({ - data: { greeting: 'hello there' }, -}) - fireEvent.click(screen.getByText('Load Greeting')) -// Wait until the mocked `get` request promise resolves and +// wait until the `get` request promise resolves and // the component calls setState and re-renders. // `waitFor` waits until the callback doesn't throw an error @@ -107,16 +164,39 @@ fetch.js import React, { useState } from 'react' import axios from 'axios' +function greetingReducer(state, action) { + switch (action.type) { + case 'SUCCESS': { + return { + error: null, + greeting: action.greeting, + } + } + case: 'ERROR': { + error: action.error, + greeting: null + } + default: { + return state + } + } +} + export default function Fetch({ url }) { - const [greeting, setGreeting] = useState('') + const [{ error, greeting }, dispatch] = useReducer(greetingReducer) const [buttonClicked, setButtonClicked] = useState(false) const fetchGreeting = async () => { - const response = await axios.get(url) - const data = response.data - const { greeting } = data - setGreeting(greeting) - setButtonClicked(true) + axios.get(url) + .then((response) => { + const { data } = response + const { greeting } = data + dispatch({ type: 'SUCCESS', greeting }) + setButtonClicked(true) + }) + .catch((error) => { + dispatch({ type: 'ERROR' }) + }) } const buttonText = buttonClicked ? 'Ok' : 'Load Greeting' @@ -126,35 +206,9 @@ export default function Fetch({ url }) { - {greeting ?

{greeting}

: null} + {greeting &&

{greeting}

} + {error &&

Oops, failed to fetch!

} ) } ``` - -```jsx -expect(axiosMock.get).toHaveBeenCalledTimes(1) -expect(axiosMock.get).toHaveBeenCalledWith(url) -expect(screen.getByRole('heading')).toHaveTextContent('hello there') -expect(screen.getByRole('button')).toHaveAttribute('disabled') - -// snapshots work great with regular DOM nodes! -expect(container).toMatchInlineSnapshot(` -
-
- -

- hello there -

-
-
-`) - -// you can also use get a `DocumentFragment`, -// which is useful if you want to compare nodes across render -expect(asFragment()).toMatchSnapshot() -```