Using and Testing the Fetch API 
Tags: react, typescript, testing
Published: 2020-02-15 01:06:51

# Displaying all GoT books
This guide is about writing code that uses the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) in React and TypeScript and how to write unit tests for it. I'll show how make an application that loads all Game of Thrones books from a rest endpoint and displays the book titles. I'm not going to bother building an endpoint like that, we can use this one:
https://www.anapioficeandfire.com/api/

# Setting things up
Generate a React/TypeScript project with the following steps (You need npm that comes along when installing node.js and you can get npx by running `npm i -g npx`):


* Run `npx create-react-app usetestfetch --template typescript`

* Run the following commands to start the server:
```typescript
cd usetestfetch
npm start
```

First clean the App.tsx up to:
```typescript
import React from 'react';

const App = () => {
  return (
    <div className="App">

    </div>
  );
};

export default App;
```

We need to define what a book is. For now it only needs to have a name:
```typescript
interface Book {
    name: string
}
```
You could put every field that is returned from the anapioficeandfire.com API in the interface, but in this example I am only going to display the name.

Let's add state for the books that we will display. Since we are using a functional component instead of a class component we need to use the [useState hook](https://reactjs.org/docs/hooks-state.html) for this. If you have no experience with state in React you might want to [read up the official documentation](https://reactjs.org/docs/state-and-lifecycle.html) first.

Below I defined state that holds an array of books, and display the state in the render method.
```typescript
import React, {useState} from 'react';

interface Book {
    name: string
}

const App = () => {
  const [books, setBooks] = useState<Book[]>([]);
  return (
    <div className="App">
       {books.map((book, index) => {
         const indexToDisplay = index += 1;
         return <div key={`book${index}`}>{indexToDisplay}&nbsp;{book.name}</div>
       })}
    </div>
  );
};

export default App;
```
We render a React node for every entry in our Book[] array using the [.map() function](https://developer.mozilla.org/nl/docs/Web/JavaScript/Reference/Global_Objects/Array/map) function. If you would run this it would still not render anything, because the state is initialized with an empty array `[]`.

# Using the Fetch API
Let's add a function called getBooks that uses the Fetch API to do a GET request on https://www.anapioficeandfire.com/api/books to retrieve the books:
```typescript
import React, {useState} from 'react';

......

const App = () => {
    const [books, setBooks] = useState<Book[]>([]);

    const fetchBooks = async () => {
        const response: Response = await fetch(
            'https://www.anapioficeandfire.com/api/books',
            {headers: {'Content-Type': 'application/json'}, method: "GET"}
        );
        setBooks(await response.json());
    };

    return (
        ..........
    );
};

export default App;
```

I decided to make the fetchBooks function async to be able to use [await statements](https://developers.google.com/web/fundamentals/primers/async-functions) instead of [handling promises](https://developers.google.com/web/fundamentals/primers/promises) with onfulfilled functions. Now we need to add a way to actually call the fetchBooks function. Let's simply add a button in our render function:
```typescript
<div className="App">
   <button onClick={fetchBooks}>Get GoT books</button>
   {books.map((book, index) => {
       const indexToDisplay = index += 1;
       return <div key={`book${index}`}>{indexToDisplay}&nbsp;{book.name}</div>
   })}
</div>
```

Now run it with `npm start`, click the button and see if the titles of all Game of Thrones books are listed nicely like below:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/xirbb3g8dskbw7mpkjdq.png)

# Testing this code!
I went with React Testing Library to render the components and obtain elements. I picked Jest for doing assertions. 

My test in App.test.tsx looks like:
```typescript
import React from 'react';
import {fireEvent, render, waitForElement} from '@testing-library/react';
import App from './App';

describe('Test books api', () => {
    test('Verify if books are retrieved on button click', async () => {
        // Render the App
        const {getByText} = render(<App/>);

        // Find the button to retrieve the books
        const button = getByText('Get GoT books');
        expect(button).toBeInTheDocument();

        // Actually click the button.
        fireEvent.click(button);

        // The above statement will result in an async action, so we need to wait      
        // a bit before the books will appear:
        const book1 = await waitForElement(
            () => getByText('1 A Game of Thrones')
        );
        expect(book1).toBeInTheDocument();
    });
});
```

You can run the test with `npm test`. I prefer to run tests via IntelliJ as it gives "run test" options next to the test:
![Running unit tests from IntelliJ](https://dev-to-uploads.s3.amazonaws.com/i/oh7o0f4om8cs8tvdmwug.png)

# Mocking the Fetch API
Now this test sometimes succeeds and sometimes doesn't. Why? The test actually goes to do a GET request to anapioficeandfire.com. This makes our test depend on a stable internet connection. The default timeout for waitForElement is 4500 seconds, which can be adjusted but that isn't desired here. 

I want this to be a unit test that can be run as fast as possible. I want to run it after every commit or even after every code change locally. This way I can find out as fast as possible if my code changes break any tests. I only want my tests to fail because of changes in my own code, not due to anapioficeandfire.com being slow or offline temporarily. If the latter happens, I can't fix it anyway.

I tried a couple of ways to mock network requests and I found 'fetch-mock' the easiest to use. Install it by running: `npm install fetch-mock @types/fetch-mock  node-fetch`

Besides adding the import statement to our App.test.tsx file, (`import fetchMock from "fetch-mock";`) you should add some code in the describe block to clear the mock to avoid that tests will affect each other:
```typescript
afterEach(() => {
    fetchMock.restore();
}
```

You should add a statement that tells fetch-mock which calls should be mocked before the code in your actual test() function:
```typescript
const books = [
    {name: 'A Game of Thrones'},
    {name: 'A Clash of Kings'},
    {name: 'A Storm of Swords'},
    {name: 'The Hedge Knight'},
    {name: 'A Feast for Crows'},
    {name: 'The Sworn Sword'},
    {name: 'The Mystery Knight'},
    {name: 'A Dance with Dragons'},
    {name: 'The Princess and the Queen'},
    {name: 'The Rogue Prince'}
];
fetchMock.mock('https://www.anapioficeandfire.com/api/books', {
    body: books,
    status: 200
});
```

The test now always succeeds and runs way faster:
![Succeeding tests](https://dev-to-uploads.s3.amazonaws.com/i/p1v7sixqpxcdh44cy3nn.png)

# Testing the error scenario
Now what happens if the fetch request somehow fails. The most common situation is that the network is not available. We can easily simulate this by disabling our ethernet adapters.

![Fetch request without internet](https://dev-to-uploads.s3.amazonaws.com/i/aw1lfkkf7prug6g4bvm5.png)

End users will not even notice that this occurs. They just witness a broken button and think your website sucks. 

To prevent this we should add error handling to our code. Define state for an error message. If everything goes well, we render the titles of the book. When an error occurs, we fill the errorMessage state and render it:
```typescript
const App = () => {
    const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined);
    const [books, setBooks] = useState<Book[]>([]);
    const fetchBooks = async () => {
        try {
            const response: Response = await fetch(
                'https://www.anapioficeandfire.com/api/books',
                {headers: {'Content-Type': 'application/json'}, method: "GET"});
            setBooks(await response.json());
            setErrorMessage(undefined);
        } catch (cause) {
            setErrorMessage('We were unable not retrieve any books due to connection problems. Please check your internet connection.');
        }
    };

    const displayBooks = () => {
        return (
            <div>
                {books.map((book, index) => {
                        const indexToDisplay = index += 1;
                        return <div key={`book${index}`}>{indexToDisplay}&nbsp;{book.name}</div>
                })}
            </div>
        );
    };

    return (
        <div className="App">
            <button onClick={fetchBooks}>Get GoT books</button>
            {errorMessage ? <p>Error: {errorMessage}</p> : displayBooks()}
        </div>
    );
};
```

Here is what this looks like:
![Error message](https://dev-to-uploads.s3.amazonaws.com/i/wmn7fk4h2l3q0pn8gmxq.png)

Let's add a test with an error scenario like this:
```typescript
test('Verify if books are retrieved on button click - error no internet', async () => {
    fetchMock.mock(
        'https://www.anapioficeandfire.com/api/books',
        Promise.reject('TypeError: Failed to fetch')
    );

    // Render the App
    const {getByText} = render(<App/>);

    // Find the button to retrieve the books
    const button = getByText('Get GoT books');
    expect(button).toBeInTheDocument();

    fireEvent.click(button);

    const errorMessage = await waitForElement(() => getByText('Error: We were unable not retrieve any books due to connection problems. Please check your internet connection.'));
    expect(errorMessage).toBeInTheDocument();
});
```

We didn't cover all cases though! The anapioficeandfire could start returning 400 or 500 HTTP responses. The Fetch API doesn't see these as errors/exceptions, but we can easily build in some validation on the Response object with some if statements:
```typescript
const fetchBooks = async () => {
    try {
        const response: Response = await fetch(
            'https://www.anapioficeandfire.com/api/books',
            {headers: {'Content-Type': 'application/json'}, method: "GET"});
        
        if (response.status === 200) {
            setBooks(await response.json());
            setErrorMessage(undefined);
        } else if (response.status === 404) {
            setErrorMessage('The server could not find this page.');
        } else {
            setErrorMessage('The server did not respond the data we wanted. We apologize for the inconvenience.');
        }
    } catch (cause) {
        setErrorMessage('We were unable not retrieve any books due to connection problems. Please check your internet connection.');
    }
};
```

If you want to manually test this code out locally, you can easily change the url into `https://www.anapioficeandfire.com/api/noneexistingpage` to force getting a 404.

Let's see how we can write tests that mock a 404 error:
```typescript
test('Verify if books are retrieved on button click - error page not found', async () => {
    fetchMock.mock('https://www.anapioficeandfire.com/api/books', {
        status: 404
    });

    const {getByText} = render(<App/>);
    const button = getByText('Get GoT books');
    fireEvent.click(button);

    const errorMessage = await waitForElement(() => getByText('Error: The server could not find this page.'));
    expect(errorMessage).toBeInTheDocument();
});
```

# That's all for now!
You can find [the entire sample repository on GitHub](https://github.com/Leejjon/usetestfetch).

Feel free to ask my any questions. Although I'm enthusiastic about React & TypeScript, I haven't used these for a professional project yet. So if you're a veteran on the subject and see bad things in my example please let me know!