@@ -11,8 +11,7 @@ In this guide, we will show you how to mock network requests and guard your test
11
11
and unmocked/unhandled network requests
12
12
13
13
::: info
14
- To simulate a real-world scenario, we will use
15
- the [ Random User Generator API] ( https://randomuser.me/ ) that provides random user data.
14
+ To simulate a real-world scenario, we will use the [ Random User Generator API] ( https://randomuser.me/ ) that provides random user data.
16
15
:::
17
16
18
17
## Phonebook Example
@@ -87,7 +86,7 @@ export default async (): Promise<User[]> => {
87
86
};
88
87
```
89
88
90
- We do the same for fetching the favorites, but this time limiting the results to 10.
89
+ We have similar function for fetching the favorites, but this time limiting the results to 10.
91
90
92
91
``` tsx title=network-requests/api/getAllFavorites.ts
93
92
import { User } from ' ../types' ;
@@ -202,53 +201,60 @@ const styles = ...
202
201
203
202
## Start testing with a simple test
204
203
205
- In our test we would like to test if the ` PhoneBook ` component renders the ` FavoritesList `
204
+ In our initial test we would like to test if the ` PhoneBook ` component renders the ` FavoritesList `
206
205
and ` ContactsList ` components correctly.
207
- We will mock the network requests and their responses to ensure that the component behaves as
208
- expected. We will use [ MSW (Mock Service Worker)] ( https://mswjs.io/docs/getting-started ) to mock the network requests .
206
+ We will need to mock the network requests and their corresponding responses to ensure that the component behaves as
207
+ expected. To mock the network requests we will use [ MSW (Mock Service Worker)] ( https://mswjs.io/docs/getting-started ) .
209
208
210
- ``` tsx title=network-requests/Phonebook.test.tsx
209
+ ::: note
210
+ We recommend using the Mock Service Worker (MSW) library to declaratively mock API communication in your tests instead of stubbing ` fetch ` , or relying on third-party adapters.
211
+ :::
211
212
212
213
::: info
213
- We recommend using the Mock Service Worker ( MSW ) library to declaratively mock API communication in
214
- your tests instead of stubbing ` fetch, or relying on third-party adapters .
214
+ You can install MSW by running ` npm install msw --save-dev ` or ` yarn add msw --dev ` .
215
+ More info regarding installation can be found in [ MSW's getting started guide ] ( https://mswjs.io/docs/getting-started#step-1-install ) .
215
216
217
+ Please make sure you're also aware of [ MSW's setup guide] ( https://mswjs.io/docs/integrations/react-native ) .
218
+ Please be minded that the MSW's setup guide is potentially incomplete and might contain discrepancies/missing pieces.
216
219
:::
217
220
218
221
``` tsx title=network-requests/Phonebook.test.tsx
219
- import {render , waitForElementToBeRemoved } from ' @testing-library/react-native' ;
222
+ import { render , screen , waitForElementToBeRemoved } from ' @testing-library/react-native' ;
220
223
import React from ' react' ;
221
224
import PhoneBook from ' ../PhoneBook' ;
222
- import {User } from ' ../types' ;
223
- import axios from ' axios' ;
224
- import {MismatchedUrlError } from ' ./test-utils' ;
225
-
226
- const ensureUrlMatchesBaseUrl = (url : string ) => {
227
- if (! url .includes (' https://randomuser.me/api' )) throw new MismatchedUrlError (url );
228
- };
229
-
230
- export const mockAxiosGetWithSuccessResponse = () => {
231
- (axios .get as jest .Mock ).mockImplementationOnce ((url ) => {
232
- // Ensure the URL matches the base URL of the API
233
- ensureUrlMatchesBaseUrl (url );
234
-
235
- return Promise .resolve ({ data: DATA });
236
- });
237
- };
225
+ import { User } from ' ../types' ;
226
+ import {http , HttpResponse } from " msw" ;
227
+ import {setupServer } from " msw/node" ;
228
+
229
+ // Define request handlers and response resolvers for random user API.
230
+ // By default, we always return the happy path response.
231
+ const handlers = [
232
+ http .get (' https://randomuser.me/api/*' , () => {
233
+ return HttpResponse .json (DATA );
234
+ }),
235
+ ];
236
+
237
+ // Setup a request interception server with the given request handlers.
238
+ const server = setupServer (... handlers );
239
+
240
+ // Enable API mocking via Mock Service Worker (MSW)
241
+ beforeAll (() => server .listen ());
242
+ // Reset any runtime request handlers we may add during the tests
243
+ afterEach (() => server .resetHandlers ());
244
+ // Disable API mocking after the tests are done
245
+ afterAll (() => server .close ());
238
246
239
247
describe (' PhoneBook' , () => {
240
- it (' fetches favorites successfully and renders all users avatars' , async () => {
241
- // Mock the axios.get function to return the data we want
242
- mockAxiosGetWithSuccessResponse ();
243
- render (<PhoneBook />);
248
+ it (' fetches all contacts and favorites successfully and renders lists in sections correctly' , async () => {
249
+ render (<PhoneBook />);
244
250
245
- // Wait for the loader to disappear
246
- await waitForElementToBeRemoved (() => screen .getByText (/ figuring out your favorites/ i ));
251
+ await waitForElementToBeRemoved (() => screen .getByText (/ users data not quite there yet/ i ));
252
+ expect (await screen .findByText (' Name: Mrs Ida Kristensen' )).toBeOnTheScreen ();
253
+ expect (
await screen .
findByText (
' Email: [email protected] ' )).
toBeOnTheScreen ();
254
+ expect (await screen .findAllByText (/ name/ i )).toHaveLength (3 );
247
255
expect (await screen .findByText (/ my favorites/ i )).toBeOnTheScreen ();
248
- // All the avatars should be rendered
249
256
expect (await screen .findAllByLabelText (' favorite-contact-avatar' )).toHaveLength (3 );
250
257
});
251
- });
252
258
253
259
const DATA: { results: User [] } = {
254
260
results: [
@@ -271,46 +277,61 @@ const DATA: { results: User[] } = {
271
277
cell: ' 123-4567-890' ,
272
278
},
273
279
// For brevity, we have omitted the rest of the users, you can still find them in
274
- // examples/cookbook/app/advanced /__tests__/PhoneBook. test.tsx
280
+ // examples/cookbook/app/network-requests /__tests__/test-utils.ts
275
281
...
276
282
],
277
283
};
278
-
279
284
` ` `
280
285
286
+ :::info
287
+ More info regarding how to describe the network using request handlers, intercepting a request and handling its response can be found in the [MSW's documentation](https://mswjs.io/docs/getting-started#step-2-describe).
288
+ :::
289
+
281
290
## Testing error handling
282
291
283
- As we are dealing with network requests, we should also test how our application behaves when the
284
- API
285
- request fails. We can mock the ` axios.get ` function to throw an error and test if our application is
286
- handling the error correctly.
292
+ As we are dealing with network requests, and things can go wrong, we should also cover the case when
293
+ the API request fails. In this case, we would like to test how our application behaves when the API request fails.
287
294
288
- ::: note
295
+ :::info
289
296
It is good to note that Axios throws auto. an error when the response status code is not in the
290
297
range of 2xx.
291
298
:::
292
299
293
300
` ` ` tsx title = network - requests / Phonebook .test .tsx
294
301
...
295
302
296
- export const mockAxiosGetWithFailureResponse = () => {
297
- (axios .get as jest .Mock ).mockImplementationOnce ((url ) => {
298
- ensureUrlMatchesBaseUrl (url );
299
-
300
- return Promise .reject ({ message: ' Error fetching favorites' });
301
- });
303
+ const mockServerFailureForGetAllContacts = () => {
304
+ server .use (
305
+ http .get (' https://randomuser.me/api/' , ({ request }) => {
306
+ // Construct a URL instance out of the intercepted request.
307
+ const url = new URL (request .url );
308
+ // Read the "results" URL query parameter using the "URLSearchParams" API.
309
+ const resultsLength = url .searchParams .get (' results' );
310
+ // Simulate a server error for the get all contacts request.
311
+ // We check if the "results" query parameter is set to "25"
312
+ // to know it's the correct request to mock, in our case get all contacts.
313
+ if (resultsLength === ' 25' ) {
314
+ return new HttpResponse (null , { status: 500 });
315
+ }
316
+ // Return the default response for all other requests that match URL and verb. (in our case get favorites)
317
+ return HttpResponse .json (DATA );
318
+ }),
319
+ );
302
320
};
303
321
304
- it (' fails to fetch favorites and renders error message' , async () => {
305
- // Mock the axios.get function to throw an error
306
- mockAxiosGetWithFailureResponse ();
307
- render (<PhoneBook />);
308
-
309
- // Wait for the loader to disappear
310
- await waitForElementToBeRemoved (() => screen .getByText (/ figuring out your favorites/ i ));
311
- // Error message should be displayed
312
- expect (await screen .findByText (/ error fetching favorites/ i )).toBeOnTheScreen ();
322
+ describe (' PhoneBook' , () => {
323
+ ...
324
+ it (' fails to fetch all contacts and renders error message' , async () => {
325
+ mockServerFailureForGetAllContacts ();
326
+ render (<PhoneBook />);
327
+
328
+ await waitForElementToBeRemoved (() => screen .getByText (/ users data not quite there yet/ i ));
329
+ expect (
330
+ await screen .findByText (/ an error occurred: error fetching contacts/ i ),
331
+ ).toBeOnTheScreen ();
332
+ });
313
333
});
334
+
314
335
` ` ` `
315
336
316
337
## Global guarding against unwanted API requests
0 commit comments