Mocking Axios GET Requests: A Practical Guide for Reliable React Testing using Jest
Tags: webdev, jest, testing, react
Published: 2024-01-23 08:53:43

For projects involving API data fetching, testing can be challenging, requiring actual API calls to validate results. However, this approach introduces issues like expense and speed variations due to network conditions. Mocking emerges as a solution, enabling developers to isolate their testing environment from external dependencies.

**What Is Mocking?**

Mocking is a technique that substitutes real objects or functions with simulated versions during testing. This practice allows developers to isolate components, control responses, and improve test speed and reliability.

During my exploration, I stumbled upon a fantastic article by Volodymyr Hudyma, titled [3 Ways To Mock Axios In Jest](https://vhudyma-blog.eu/3-ways-to-mock-axios-in-jest/). The article outlines three approaches to mocking Axios, with two involving additional packages.

In this guide, we'll create a component utilizing Axios to make a GET request for fetching a list of users and rendering it in the DOM. Through this example, we'll dive into one approach to mocking in Jest.

The objective of this comprehensive guide is to provide you with a clear understanding of how to efficiently mock external API response when utilizing Axios, ensuring a consistent and reliable testing environment.

**Prerequisites:**

- JavaScript Fluency: Understanding JavaScript is essential for working with React and testing frameworks.
- React Basics: Familiarity with React components and their lifecycle is required to follow the tutorial effectively.

**Project Setup**

- Component Creation: We'll build a React component that uses Axios to retrieve a user list and render it on the screen.


```
import { useEffect, useState } from "react"
import axios from "axios"                                                                       



function UserList() {
    const [userList, setUserList] = useState([])
    
    useEffect(()=>{
        const fetchUserList = async() =>{
            try{
                await axios.get("https://jsonplaceholder.typicode.com/users").then((res) => setUserList(res.data))
            } catch(err) {
                console.log(err)
            }
        }

        fetchUserList()
    },[])

    return (
        <div>
            <h2>Users</h2>
            {
                userList.map((user)=>{
                    return(
                        <div key={user.id}>
                            <p>{user.name}</p>
                            <p>{user.email}</p>
                        </div>
                    )
                })
            }
        </div>
    )
}

export default UserList
```
This component serves as a fundamental illustration of making an API call using the useEffect and useState hooks to manage state changes before the DOM reloads.

To effectively test this component, it's essential to isolate it from the external API and external modules by mocking Axios and controlling its response when called. This approach grants us complete control over the component, allowing precise interaction during testing. Below is an example of how our "userList.test.js" file should be structured:

```
import { render, screen, waitFor } from "@testing-library/react";
import UserList from "./userList";
import axios from "axios";
//mock the axios module
jest.mock("axios");

const dummyUserList = [
  {
    userId: 1,
    id: 1,
    name: "Nacho",
    email: "nacho@gmail.com"
  },
  {
    userId: 1,
    id: 2,
    name: "Nochi",
    email: "nochi@gmail.com"
  },
  {
    userId: 1,
    id: 3,
    name: "Davidszn",
    email: "david@gmail.com"
  },
];

describe("User component", () => {
  it("should render user header", async () => {
    render(<UserList />);

    // Wait for the asynchronous data fetching to complete
    await waitFor(() => {
      expect(screen.getByText(/users/i)).toBeInTheDocument();
    });
  });
  it("should render user list after fetching list", async ()=>{
    //resolving the get request with the dummyUserList
    axios.get.mockResolvedValue({ data: dummyUserList });

    // or you could use the following depending on your use case:
    // axios.get.mockImplementation(() => Promise.resolve(dummyUserList))

    render(<UserList/>)
    //another alternative to wait for block is findby queries
    expect(await screen.findByText(/nacho@gmail.com/i)).toBeInTheDocument()

    // Optionally, you can check for the number of calls to axios.get
    expect(axios.get).toHaveBeenCalledTimes(1);
  })
});

```

The Jest mock method allows us to mocks a module and returns an auto-mocked version when required during the test. Test 1 ensures the component renders the header text "users" after asynchronous data fetching. Utilize [waitFor](https://testing-library.com/docs/dom-testing-library/api-async/#waitfor) method to wait for the asynchronous operation to complete. Test 2 simulates the GET request using `axios.get.mockResolvedValue` method to resolve the response with the _dummyUserList_ array then checks if the user list renders after fetching, using [screen.findByText](https://testing-library.com/docs/queries/about) method. We can also optionally verify the number of calls to `axios.get`. There are also various Jest mock methods such as [jest.fn](https://jestjs.io/docs/mock-function-api#jestfnimplementation), [jest.spyOn](https://jestjs.io/docs/jest-object#jestspyonobject-methodname), depending on the specific need or task.

If you run the test using `npm test`, you may encounter the following result:.

```
    SyntaxError: Cannot use import statement outside a module

      1 | import { useEffect, useState } from "react"
    > 2 | import axios from "axios"
```

**How to fix the "Cannot use import statement outside a module" for axios libary?**

This issue arises from a clash between the testing environment and the module, often related to the use of different JavaScript module systems (CommonJS vs. ES6 modules). It's a relatively rare but known issue, especially with Axios.

A potential fix for this problem involves adding a few specific lines to your "package.json" file:

```
"type": "module"
```
This informs Node.js that your project is using ES6 modules.
```
  "jest": {
    "transformIgnorePatterns": ["node_modules/(?!axios)/"]
}
```
The "transformIgnorePatterns" option is a solution that tells Jest to ignore transformation for files inside node_modules except for those in the axios module. Your "package.json" file should be configured as follows:

```
{
  "type": "module",
  "scripts": {
    "test": "npm test",
     // other scripts
  },
  "dependencies": {
    // your dependencies
  },
  "jest": {
    "transformIgnorePatterns": ["node_modules/(?!axios)/"]
}
  // other configurations
}
```
After restarting the test, you should obtain the following results

```
 PASS  src/user/userCard.test.js
  User component
    √ should render user header (327 ms)
    √ should render user list after fetching list (26 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        4.893 s
Ran all test suites matching /userCard.test.js/.
```
The test passed because we were able to intercept the axios 'GET' request and changed the response to our dummyList so the Dom renders data from the dummyList, which use to test the component.

I hope this article has simplified the concept of mocking in React testing, especially in the context of handling bugs related to module systems. By intentionally choosing axios libary, I aimed to share a practical solution to a common issue I faced earlier on. Our exploration into the lost art of testing in React is far from over, and I'm excited to announce that the journey continues with the upcoming article on testing a react app using Redux.

Stay jiggy, and happy testing!

**References**

- https://codedamn.com/news/nodejs/cannot-use-import-statement-outside-a-module
- https://www.csrhymes.com/2022/03/09/mocking-axios-with-jest-and-typescript.html
- https://www.reddit.com/r/reactjs/comments/176a5hh/getting_syntaxerror_cannot_use_import_statement/?rdt=42688&onetap_auto=true