Mock axios error

Have created unit test cases using axios mock approach and do see below console errors. Have tried many approaches but none resolved the problem. Very new to REACT/JEST community, but trying best to

Have created unit test cases using axios mock approach and do see below console errors. Have tried many approaches but none resolved the problem. Very new to REACT/JEST community, but trying best to resolve this.

Expectation:

  1. All test cases including success, error scenarios should be 100% covered and should pass with no warnings/errors.
  2. Response should be tested with empty results list and as well with non-empty results list.
  3. Error scenarios due to timeout/network should also be handled.

Errors:


Expected: undefined
Received: {"results": []}

(node:76675) UnhandledPromiseRejectionWarning: 
Unhandled promise rejection. This error originated 
either by throwing inside of an async function without a catch block, 
or by rejecting a promise which was not handled with .catch(). 
(rejection id: 1)

(node:76675) [DEP0018] DeprecationWarning: Unhandled promise rejections 
are deprecated. In the future, promise rejections that are not handled will 
terminate the Node.js process with a non-zero exit code.

What I tried:

index.js

export default getAreas = area => axios.get(`/test/areas/${area}`);

__mocks__/axios.js

const axiosMocked = {
  get: jest.fn(() => Promise.resolve({ results: [] }))
};
export default axiosMocked;

__tests__/index.test.js

import mockAxios from 'axios';
import getAreas from '../index';

afterEach(() => {
  jest.clearAllMocks();
});

it('fetches results from api', () => {
  mockAxios.get.mockImplementationOnce(() => Promise.resolve({ results: [] }));
  getAreas('atl').then(response => {
    expect(response).toEqual();
  });
  expect(mockAxios.get).toHaveBeenCalledTimes(1);
  expect(mockAxios.get).toHaveBeenCalledWith('/test/areas/atl');
});

skyboyer's user avatar

skyboyer

21.4k7 gold badges55 silver badges63 bronze badges

asked Oct 12, 2019 at 18:09

user12133234's user avatar

2

Here is a solution using jest.mock method to mock axios manually without using any third-party mock library.

index.ts:

import axios from 'axios';

const getAreas = area => axios.get(`/test/areas/${area}`);

export default getAreas;

index.spec.ts:

import getAreas from './';
import axios from 'axios';

jest.mock('axios');

afterEach(() => {
  jest.clearAllMocks();
});

describe('getAreas', () => {
  it('fetches results from api', () => {
    (axios.get as jest.Mock).mockResolvedValueOnce({ results: [] });
    getAreas('atl').then(response => {
      expect(response).toEqual({ results: [] });
    });
    expect(axios.get).toHaveBeenCalledTimes(1);
    expect(axios.get).toHaveBeenCalledWith('/test/areas/atl');
  });
});

Unit test result with 100% coverage:

 PASS  src/stackoverflow/58357043/index.spec.ts (7.557s)
  getAreas
    ✓ fetches results from api (9ms)

----------|----------|----------|----------|----------|-------------------|
File      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files |      100 |      100 |      100 |      100 |                   |
 index.ts |      100 |      100 |      100 |      100 |                   |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        8.952s

Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58357043

answered Oct 14, 2019 at 5:26

Lin Du's user avatar

Lin DuLin Du

76.6k74 gold badges242 silver badges429 bronze badges

1

What’s this?

This is a light-weight, easy to use synchronous Axios mock for unit testing with Jest.

Why would I use it?

Because it works synchronously, meaning that your tests will be easier to write, read and understand.

Can it be used with Jasmine/Mocha?

Unfortunately out of the box this mock works only with Jest.

However, if you look at the source code, you can see that it uses Jest only to define spies (for methods post, get, put, patch, delete, create, all, head, options, request). This means that it can easily be modified to use any other testing framework — go to GitHub, clone it, modify it, play with it :)

What’s in this document?

  • Installation
    • Why do we need to manually create the mock?
  • Basic example
  • Axios mock API
    • axios.mockResponse
    • axios.mockResponseFor
    • axios.mockError
    • axios.lastReqGet
    • axios.getReqByMatchUrl
    • axios.getReqByRegex
    • axios.lastPromiseGet
    • axios.reset
  • Additional examples
    • Values returned by lastReqGet and lastPromiseGet methods
    • Resolving requests out of order
  • Missing features
  • Synchronous promise

Installation

Installation is simple — just run:

npm i --save-dev jest-mock-axios

Next you need to setup a manual Jest mock for Axios (we’ll explain why a bit later):

  • create __mocks__ directory in your project root (or whatever is configured in the roots config in jest.config.js — when using react-scripts this is <rootDir>/src, so you need to place it under src/__mocks__)
  • inside this new directory create a files named axios.js
  • copy & paste the following snippets to axios.js file
// ./__mocks__/axios.js
import mockAxios from 'jest-mock-axios';
export default mockAxios;

⚠️ In v4.6.0 this module is inadvertently a pure ES module (#83). Please use v4.5.0 or v4.6.1 if you encounter any problems for now.

Why do we need to manually create the mock?

It’s because Jest expects mocks to be placed in the project root, while
packages installed via NPM get stored inside node_modules subdirectory.

Basic example

Let’s consider that we want to test a component which uses Axios. This component returns a promise, which will be resolved after Axios is done communicating with the server.

Here’s a Jest snippet, which explains how we would test this component:

// ./test/UppercaseProxy.spec.js
import mockAxios from 'jest-mock-axios';
import UppercaseProxy from '../src/UppercaseProxy';

afterEach(() => {
    // cleaning up the mess left behind the previous test
    mockAxios.reset();
});

it('UppercaseProxy should get data from the server and convert it to UPPERCASE', () => {

    let catchFn = jest.fn(),
        thenFn = jest.fn();

    // using the component, which should make a server response
    let clientMessage = 'client is saying hello!';

    UppercaseProxy(clientMessage)
        .then(thenFn)
        .catch(catchFn);

    // since `post` method is a spy, we can check if the server request was correct
    // a) the correct method was used (post)
    // b) went to the correct web service URL ('/web-service-url/')
    // c) if the payload was correct ('client is saying hello!')
    expect(mockAxios.post).toHaveBeenCalledWith('/web-service-url/', {data: clientMessage });

    // simulating a server response
    let responseObj = { data: 'server says hello!' };
    mockAxios.mockResponse(responseObj);

    // checking the `then` spy has been called and if the
    // response from the server was converted to upper case
    expect(thenFn).toHaveBeenCalledWith('SERVER SAYS HELLO!');

    // catch should not have been called
    expect(catchFn).not.toHaveBeenCalled();
});

To make this example complete and easier to understand, let’s have a look at a (verbose) implementation of component we are testing:

// ./src/UppercaseProxy.js
import axios from 'axios';

const UppercaseProxy = (clientMessage) => {

    // requesting data from server
    let axiosPromise = axios.post('/web-service-url/', { data: clientMessage });

    // converting server response to upper case
    axiosPromise = axiosPromise.then(serverData => serverData.data.toUpperCase());

    // returning promise so that client code can attach `then` and `catch` handler
    return(axiosPromise);
};

export default UppercaseProxy;

At the bottom of this page you can find additional examples.

Axios mock API

In addition to standard Axios methods (post, get, put, patch, delete, create, all, head, options, request, axios(url)), which are exposed as spies, Axios mock has additional public methods, which are intended to facilitate mocking:

  • mockResponse — simulates a server (web service) response
  • mockError — simulates a (network/server) error
  • lastReqGet — returns extended info about the most recent request
  • getReqMatching — returns extended info about the most recent request matching the given criteria (url, method and params)
  • getReqByMatchUrl — returns extended info about the most recent request matching the given regexUrl.
  • getReqByRegex — returns extended info about the most recent request matching the given keys and regexUrls.
  • queue — returns a queue with all requests received.
  • lastPromiseGet — returns promise created when the most recent request was made
  • reset — resets the Axios mock object — prepare it for the next test (typically used in afterEach)

Note: all is just an alias to Promise.all (as it is in axios). Thus you can use it with mockResponse, but you can still retrieve statistics for it. Mock the requests used in all instead.

axios.mockResponse(response[, requestInfo[, silentMode]])

After a request has been made to the server (web service), this method resolves that request by simulating a server response. Status meaning is ignored, i.e. 400 will still resolve axios promise. Use mockError for non-2xx responses.
NOTE: This method should be called after the axios call in your test for the promise to resolve properly.

Arguments: response

The first argument of this method is the a response object returned by the server, with a structure illustrated by the snippet below. All the properties are optional, meaning that if a property is ommitted it will be replaced by a default value (defaults are shown in the snippet).

response = {
    data: {},
    status: 200,
    statusText: 'OK',
    headers: {},
    config: {},
}

The given response object will get passed to then even handler function.

Arguments: (optional) requestInfo

The second argument enables us to pinpoint an exact server request we wish to resolve. This can be useful if we’re making multiple server requests and are planing to resolve them in a different order from the one in which they were made.

We supply two different objects:

  • an extended request info object, which can be accessed by calling lastReqGet method
  • a promise object, which can be accessed by calling the lastPromiseGet method

If ommited this argument defaults to the latest request made (internally the lastReqGet method is called).

At the end of this document you can find an example which demonstrates how this parameter can be used.

Arguments: (optional) silentMode

Both mockResponse and mockError will throw an error if you’re trying to respond to no request, as this usually means you’re doing something wrong.
You can change this behavior by passing true as third argument, activating the so-called silentMode. With silentMode activated, the methods will just do nothing.

axios.mockResponseFor(criteria, response[, silentMode])

This behaves very similar to mockResponse, but you explicitly specify the request you want to respond to by
specifying an object containing url and/or method, or just a plain string (to match by URL only).

Example:

mockAxios.mockResponseFor({url: '/get'}, {data: "test"});

Arguments: criteria

An object or string (the url) specifying which request to match. Currently url and method are supported for the object. If both url and method are passed, it only responds to requests matching both. If multiple requests match against the criteria, the most recent one is responded to.

Arguments: response

The second argument is a response object, which works the same way as described part about the mockResponse method.

Arguments: (optional) silentMode

The third argument is the silentMode flag, which works the same way as described part about the mockResponse method.

axios.mockError(err[, requestInfo])

This method simulates an error while making a server request (network error, server error, etc …).
NOTE: This method should be called after the axios call in your test for the promise to resolve properly.

Arguments: err

Error object will get passed to catch event handler function. If omitted it defaults to an empty object.

Arguments: (optional) requestInfo

The second argument is a requestInfo object, which works the same way as described part about the mockResponse method.

Arguments: (optional) silentMode

The third argument is the silentMode flag, which works the same way as described part about the mockResponse method.

axios.lastReqGet()

lastReqGet method returns extended info about the most recent request. The returned value can be used to pinpoint exact server request we wish to resolve (the value is passed as the second param of mockResponse or mockError methods).

The returned info contains all the data relevant to the request. It has the following structure (an example):

let requestInfo = {
    // promise created while
    promise: SimplePromise,
    // URL passed to the get/post/head/delete method
    url: "https://github.com/",
    // data which was pased to the get/post/head/delete method
    data: { text: "this is payload sent to the server" },
    // config which was pased to the get/post/head/delete method
    config: {
        ... something ...
    }
}

Additional examples at the end of this document illustrate how this method can be used.

NOTE: this is a sibling method to the lastPromiseGet (which returns only the promise portion of this the request object).

If no request has been made yet, returns undefined.

axios.getReqMatching(criteria)

getReqMatching() returns the same info about a specific request as lastReqGet (see above). Instead of returning
the most recent request, it returns the most recent request matching the given criteria or undefined if no such request could be found.

Arguments: criteria

An object specifying which request to match. Currently url, method and params are supported.

axios.getReqByUrl(url)

getReqByUrl() returns the same info about a specific request as lastReqGet (see above). Instead of returning the
most recent request, it returns the most recent request matching the given url or undefined if no such request could be found.

Arguments: url

The url to be matched. Must match exactly the url passed to axios before.

axios.getReqByMatchUrl(regexUrl)

getReqByMatchUrl() returns the same info about a specific request as lastReqGet (see above). Instead of returning the
most recent request, it returns the most recent request with a url that matches the given regexUrl or undefined if no such request could be found.

Arguments: regexUrl

The regexUrl matcher. Must contain a Regex object RegExp(/.../).

Usage

const req = mockAxios.getReqByMatchUrl(/resource/d+/create/)
mockAxios.mockResponse({ data: { id: 1 } }, req)

axios.getReqByRegex(opts)

getReqByRegex() returns the same info about a specific request as getReqByMatchUrl() (see above). Instead of matching only the url against a RegexUrls, it’s possible to match any keys against RegexUrls.

It returns the most recent request with key(s) that match(es) the given RegexUrl(s) or undefined if no such request could be found.

Arguments: opts

The keys + regexes matchers.

Must contain pairs of keys and a Regex objects RegExp(/.../) to be tested against the requests.

{ key_a: RegExp_a, ..., key_n: RegExp_n }

Usages

  • url that matches /batch/
const request = mockAxios.getReqByRegex({ url: /batch/ })
  • data that matches /employees/
const request = mockAxios.getReqByRegex({ data: /employees/ })
  • config that matches /my_config/
const request = mockAxios.getReqByRegex({ config: /my_config/ })
  • method that matches /delete/
const request = mockAxios.getReqByRegex({ method: /delete/ })
  • url that matches /batch/ and data that matches /employees/

multiple keys is supported ✔️

const request = mockAxios.getReqByRegex({ url: /batch/, data: /employees/ })

axios.lastPromiseGet()

lastPromiseGet method returns a promise given when the most recent server request was made. The returned value can be used to pinpoint exact server request we wish to resolve (the value is passed as the second param of mockResponse or mockError methods).

The promise object returned by this function corresponds to the one returned by post, get, put, patch, delete, head, options, request or all method inside the code we wish to test.

Additional examples at the end of this document illustrate how this method can be used.

NOTE: This is a sibling method to the lastReqGet, which in addition to promise returns object containing extended info about the request.

axios.reset()

reset method clears state of the Axios mock to initial values. It should be called after each test, so that we can start fresh with our next test (i.e. from afterEach method).

Additional examples

Since AxiosMock is relatively simple, most of its functionality was covered in basic example at the beginning of this document. In this section we’ll explore features not covered by that initial example.

Values returned by lastReqGet and lastPromiseGet methods

The following example illustrates the meaning of the values returned by lastReqGet and lastPromiseGet methods.

The first snippet shows a component which will be tested. The component makes a post request to the server and stores the promise returned by Axios.

// ./src/MyComponent.js
import axios from '../lib/index';

class MyComponent {

    CallServer () {
        // making a `post` request and storing the given promise
        this.axiosPromise = axios.post('/web-service-url/', { data: clientMessage });
    }
}

export default MyComponent;

In our spec file we will compare promise stored inside the MyComponent with values returned by lastReqGet and lastPromiseGet methods:

// ./test/MyComponent.spec.js
    import MyComponent from '../src/SomeSourceFile';

    let myComp = new MyComponent();

    myComp.CallServer();

    // getting the extended info about the most recent request
    let lastReqInfo = MockAxios.lastReqGet();
    // getting the promise made when the most recent request was made
    let lastPromise = MockAxios.lastPromiseGet();

    // the following expression will write `true` to the console
    // > here we compare promise stored in the `MyComponent` to the one
    //   returned by the `lastPromiseGet` method
    console.log(myComp.axiosPromise === lastPromise);

    // the following expression will also write `true` to the console
    // > here we compare promise stored in the `MyComponent`
    //   to the one in the request info, which was returned by the
    //   `lastReqGet` method
    console.log(myComp.axiosPromise === lastReqInfo.promise);

    // the following will also write "true" to console,
    // since it't the same object
    console.log(lastPromise ===  lastReqInfo.promise);

Resolving requests out of order

In the following example we’ll have a look at how to resolve requests at desired order by using lastReqGet method.

In this example we’ll create two consecutive requests before simulating a server response to the first one.

it('when resolving a request an appropriate handler should be called', () => {

    let thenFn1 = jest.fn(),
        thenFn2 = jest.fn();

    // creating the FIRST server request
    UppercaseProxy('client is saying hello!').then(thenFn1);
    // storing the request info - we'll need it later to pinpoint the request
    let firstRequestInfo = mockAxios.lastReqGet();

    // creating the SECOND server request
    // BEFORE the first had chance to be resolved
    UppercaseProxy('client says bye bye!').then(thenFn2);

    // Simulating a server response to the FIRST request
    // -> we're using request info object to pinpoint the request
    // ... IF the info object is ommited, the method would automatically
    // resolve to the newest request from the internal queue (the SECOND one)
    mockAxios.mockResponse({ data: 'server says hello!' }, firstRequestInfo);

    // only the first handler should have been called
    expect(thenFn1).toHaveBeenCalled();
    expect(thenFn2).not.toHaveBeenCalled();

    // Simulating a server response to the SECOND request
    // NOTE: here we don't need to provide the request info,
    // since there is only one unresolved request left
    // -> `mockResponse` resolves the last request in the
    //     queue if request info is ommited
    mockAxios.mockResponse({ data: 'server says bye bye!' });

    // the first `then` handles should be called only once
    expect(thenFn1).toHaveBeenCalledTimes(1);
    // now the second `then` handler should be called
    expect(thenFn2).toHaveBeenCalled();
});

Although this might not be the most realistic use-case of this functionality, it does illustrate how lastReqGet method can be used to alter the default behaviour of the mockResponse method.

NOTE: the identical effect can be achieved by using the lastPromiseGet method. These two methods perform a similar task, as described in the corresponding documentation.

Using await and async

You can also use this library with await and async. Given the following async function (same as above):

const UppercaseProxy = async (clientMessage) => {
    const serverData = await axios.post("/web-service-url/", { data: clientMessage });

    return serverData.data.toUpperCase();
};

The function can be tested like this (basically the same idea as in the first example at the top):

it("UppercaseProxy should get data from the server and convert it to UPPERCASE", async () => {
    const clientMessage = "client is saying hello!";

    const promise = UppercaseProxy(clientMessage);

    expect(mockAxios.post).toHaveBeenCalledWith("/web-service-url/", {
        data: clientMessage,
    });

    // simulating a server response
    const responseObj = { data: "server says hello!" };
    mockAxios.mockResponse(responseObj);

    const result = await promise;

    expect(result).toEqual("SERVER SAYS HELLO!")
});

Interceptors

AxiosMock offers basic support for interceptors (i.e. it does not break when interceptors are used in tested code).

Cancelling requests

jest-mock-axios has basic support for cancelling requests as in axios. Please note that you will get an error if you try to mock a response for a request after it has been cancelled. Please refer to the provided test case for further usage details.

Missing features

AxiosMock covers the most popular parts of Axios API, meaning that some of the features are missing or only partially implemented (i.e. interceptors).

If you need an additional feature, you can request it by creating a new issue on project’s GitHub page.

Also you are welcome to implement the missing feature yourself and make a pull request :)

Synchronous promise

The magic which enables axios mock to work synchronously is hidden away in synchronous-promise, which enables promises to be settled in synchronous manner.

Inspiration

This mock is loosely based on the following gist: tux4/axios-test.js

License

MIT License, http://www.opensource.org/licenses/MIT

React Jest provides the methods to mock the library like manual mocks, es6 class mocks. Axios library is used to fetch the data in the React component.

We should not make the http calls inside the unit test files. It helps to test both the success and error conditions of the api calls and subsequent component functionality. That is why it is better to mock the axios library.

In this tutorial, we are going to mock the axios component by using the Jest framework itself. We are not going to use any external library to mock the axios library.

Mocking axios library

We can mock the axios library using the jest.mock() function and providing a mock factory method.

Axios is an es6 class, so mocking axios will be similar to mocking an es6 class in jest. We also have to implement the default method in our mock factory function.

Below is the sample code to mock the axios library.

jest.mock('axios', () => {
  return {
    __esModule: true,
    default: jest.fn()
  }
});

We can mock other methods of axios library like axios.get, axios.post by implementing them inside the mock factory function.

jest.mock('axios', () => {
  return {
    __esModule: true,
    default: jest.fn(),
    get: jest.fn()
    ...
  }
});

Using the mock axios inside the test case

Once we have mocked the axios library in our test case, we can use the mocked library to return the resolved response or the rejected error response for the api call.

We are going to require the axios library in our test case, and provide a mock implementation.

const axios = require('axios');
jest.spyOn(axios, 'default').mockResolvedValue({
  name: 'abc'
})

In the above code snippet, we are returning the resolved value (ie the Promise is resolved with the data {name : abc}

We can also reject the data in similar fashion.

jest.spyOn(axios, 'default').mockRejectedValue()

Complete example of the mock of axios

Now, we are going to write a component which contains the axios code. Then we will write the unit test case for it by mocking the axios library using jest.

The sample component which we are going to write will be a simple React component, which will make a call to local server, and set the state from the response. If the api throws error, then the error state is set to true. Based upon the state value, then output is rendered. We are going to test both the success and error condition of axios.

Sample component is as follows

import { Component } from 'react';
import axios from 'axios';
import './App.css';
class App extends Component{
  state = {
    name: '',
    error: false
  }
  componentDidMount(){
    axios({
      url: 'http://localhost:3000',
    }).then((data)=>{
      const { name } = data;
      this.setState({
        name
      })
    }).catch(()=>{
      this.setState({
        error: true
      })
    })
  }
  render(){
    if(this.state.error){
      return "error";
    }
    return this.state.name;
  }
}
export default App;

In the above component, we import the dependencies of axios, react.

import { Component } from 'react';
import axios from 'axios';
import './App.css';

Then we declare the App component

class App extends Component{

We declare the state variable of name and error. Name will be displayed on success, error will be set when there is error in the axios call.

state = {
    name: '',
    error: false
  }

Then in the component lifecycle method, we call the api http://localhost:3000 to get the data. In the then block, we set the state of name. In the catch block, we set the error state boolean error.

 componentDidMount(){
    axios({
      url: 'http://localhost:3000',
    }).then((data)=>{
      const { name } = data;
      this.setState({
        name
      })
    }).catch(()=>{
      this.setState({
        error: true
      })
    })
  }

In the render function, we return the error message if error state is set, or else we return the name state.

render(){
    if(this.state.error){
      return "error";
    }
    return this.state.name;
  }

Writing unit test case for above sample component

The test case for the above component which mocks the axios and tests the success and error condition are as follows.

import React from 'react';
import axios from 'axios';
import { waitFor } from '@testing-library/dom';
import { render } from '@testing-library/react';
import App from './App';
jest.mock('axios');
beforeEach(()=>{
  jest.clearAllMocks()
})
test('show name', async () => {
  jest.spyOn(axios, 'default').mockResolvedValue({
    name: 'abc'
  })
  const wrapper = render(<App />);
  await waitFor(() => expect(wrapper.queryAllByText('abc')).toHaveLength(1));
});
test('show name error', async () => {
  jest.spyOn(axios, 'default').mockRejectedValue(()=>({}));
  const wrapper = render(<App />);
  await waitFor(() => expect(wrapper.container.innerHTML).toEqual('error'));
});

I am going to explain the above code one by one.

If you have written the unit test case before, then you will be familier with some of the code above.

beforeEach runs the callback function before every test. test contains the test.

Here we are going to focus on the mocking aspect.

We have imported the axios library from ‘axios’ dependency. Then we have mocked the library using jest.mock. This will replace the methods of axios with mock methods.

import axios from 'axios';
...
jest.mock('axios');

After mocking the axios library, we can spy on it and provide our own implementation for the methods.

For the first test case about the, we have resolved the value (so it will return success condition).

jest.spyOn(axios, 'default').mockResolvedValue({
  name: 'abc'
})

Then we check if the name is properly displayed or not using the following code

const wrapper = render(<App />);
await waitFor(() => expect(wrapper.queryAllByText('abc')).toHaveLength(1));

For the error condition, we are going to spy on axios and reject the value. This will mock the error in response.

jest.spyOn(axios, 'default').mockRejectedValue(()=>({}));

Then we are going to test if the error is displayed in the output using the following code

const wrapper = render(<App />);
await waitFor(() => expect(wrapper.container.innerHTML).toEqual('error'));

Published: Mar 9, 2022 by C.S. Rhymes

Recently I wanted to write a test for a React component that uses axios to retrieve information from an API. To do this I had to mock jest, but I ran into some issues with the types as I was using typescript. This article explains how I managed to get it to work.

Mocking axios

I started out by Googling and found this great article, 3 Ways To Mock Axios In Jest by Volodymyr Hudyma. In the article it provides three ways of mocking axios, two of which use additional packages, but I wanted to try the first option which was to mock axios using Jest and not have any additional dependencies.

The example app

I have created an example app using create-react-app that uses axios to retrieve a list of users and display them on the page.

View the full code repository on GitHub

npx create-react-app my-app --template typescript

The app already had @testing-library/react all set up and ready to go, I just had to add axios.

As we are using typescript, I have defined my User type as the id and the name as follows.

// App.tsx

type User = {
  id: number;
  name: string;
}

I used useState to store and update the users and then a getData method goes and gets the users and updates the state.

// App.tsx

  const [users, setUsers] = useState<User[] | undefined>(undefined);
  const getData = () => {
    axios.get('https://jsonplaceholder.typicode.com/users')
      .then((response) => {
        let responseUsers: User[] = response.data.map((responseUser: any) => {
          return {
            id: responseUser.id,
            name: responseUser.name
          }
        })
        setUsers(responseUsers);
      })
      .catch((error) => {
        console.log(error);
      })
  }

The component has a ‘Get users’ button that calls the getData method and then renders the list of returned users.

// App.tsx

  return (
    <div className="App">
      <header className="App-header">
        <button onClick={() => getData()}>Get users</button>
        <ul>
          {users?.map((user: User) => {
            return (<li key={user.id}>{user.name}</li>)
          })}
        </ul>
      </header>
    </div>
  );

Writing the test

I started out by writing a test that called the real API to ensure that it successfully called the API and returned some data. Here we use waitFor to wait for the response from the API before running the assertions. To use waitFor we need to make the test an async function.

// App.test.tsx

import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react';
import App from './App';

describe('App', () => {
  const renderComponent = () => (render(<App />));

  test('renders learn react link', async () => {

    const { getByText, getAllByRole } = renderComponent();

    fireEvent.click(getByText('Get users'));

    await waitFor(() => {
      const userList = getAllByRole('listitem');
      expect(userList).toHaveLength(10);
      expect(userList[0]).toHaveTextContent('Leanne Graham');
      expect(userList[1]).toHaveTextContent('Ervin Howell');
    });
  });
})

This test uses fireEvent.click() to simulate clicking on the ‘Get users’ button, which triggers the getData() method. We then get the list items and assert that there are 10 items and then check the names of the users are displayed correctly.

This works but what happens if the API endpoint goes down, or the sample data is updated, then the tests will fail. Also, I don’t want to be calling a real API when tests are running in GitHub actions, an environment out of my control.

The test with Mock

I started by following the example in the article and added jest.mock('axios'); after the imports. Then as per the example, I added the axios.get.mockResolvedValue({}); but straight away I got a type error.

Screenshot of the type error message

After a bit of Googling again I came across this stack overflow post which provided the solution.

import axios from 'axios';
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;

This removed the type error for the mockedAxios.get.mockResolvedValue({}) line and I was good to go. I then provided an example data return from the mocked axios with two users and updated the test so the length would be 2 and the names would be the names from my mocked data.

I have called this test AppMock.test.tsx so you can see both examples in the code repository.

// AppMock.test.tsx

import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react';
import App from './App';
import axios from 'axios';

// Mock jest and set the type
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;

describe('App', () => {
  const renderComponent = () => (render(<App />));

  test('renders learn react link', async () => {

    const { getByText, getAllByRole } = renderComponent();

    // Provide the data object to be returned
    mockedAxios.get.mockResolvedValue({
      data: [
        {
          id: 1,
          name: 'Joe Doe'
        },
        {
          id: 2,
          name: 'Jane Doe'
        }
      ],
    });

    fireEvent.click(getByText('Get users'));

    await waitFor(() => {
      const userList = getAllByRole('listitem');
      expect(userList).toHaveLength(2);
      expect(userList[0]).toHaveTextContent('Joe Doe');
      expect(userList[1]).toHaveTextContent('Jane Doe');
    });
  });
})

TypeError: Cannot read properties of undefined (reading ‘then’)

Once I had got this example React app working, I tried to apply the same changes to another React project. This had a different set up and had a different Jest configuration. For some reason the above didn’t work with the other app. I got an error message, TypeError: Cannot read properties of undefined (reading 'then').

I don’t know the reasoning for this and unfortunately I can’t find the link to the forum that had the resolution for this, but I had to change jest.Mocked<typeof axios> to jest.MockedFunction<typeof axios>.

I also had to change mockedAxios.get.mockResolvedValue() to mockedAxios.mockResolvedValue(), removing the get.

Typescript then warned me that I also needed to provide status, statusText, headers and config, as well as the data object. Once I had done this the test now passed.

    mockedAxios.mockResolvedValueOnce({
      data: [
        {
          id: 1,
          name: 'Joe Doe'
        },
        {
          id: 2,
          name: 'Jane Doe'
        }
      ],
      status: 200,
      statusText: 'Ok',
      headers: {},
      config: {},
    });

I am still a beginner when it comes to Jest so if you know why this is then please leave a comment as it would be good to know the reason why.

Photo by Tricia Gray on StockSnap

Cover image for The only 3 steps you need to mock an API call in Jest

Zak Laughton

I recently found myself working in a Javascript codebase where I needed to implement new Jest tests. I knew very little at the time about writing tests, so I looked to Jest docs and existing patterns in the codebase to figure out best practices and how to do it. It was fairly straightforward, and I even found myself enjoying testing. But I could not for the life of me reliably mock an API call.

Dog with caption "I have no idea what I'm doing"

The docs seemed clear, and the existing code appeared to have good patterns, but there were just so many ways to mock things. The existing tests used all sorts of mocking methods such as jest.genMockFromModule(), jest.spyOn(), and jest.mock(). Sometimes the mocks were inline, sometimes they were in variables, and sometimes they were imported and exported in magical ways from mysterious __mocks__ folders. I used these techniques interchangeably every time I got a burst of confidence in understanding, only to find myself stumbling over the different methods and their effects. I had no idea what I was doing.

The issue

The issue was that I was trying to learn how to run before I even knew how to walk. Jest has many powerful ways to mock functions and optimize those mocks, but they’re all useless if you don’t know how to make a simple mock in the first place. And while the Jest documentation provides a lot of great insight and techniques, I couldn’t figure out where to start.

In this article, I hope to give you absolute basics to mock an API call so you can benefit from my 2020 hindsight (heh). If you’re going crazy like I was because you can’t figure out how to just make a simple damn mock, Start here…

(NOTE: The code below was written in Node.js, but the mocking concepts also apply to frontend Javascript and ES6 modules)

The unmocked code

We’re going to be testing this getFirstAlbumTitle() function, which fetches an array of albums from an API and returns the title of the first album:

// index.js
const axios = require('axios');

async function getFirstAlbumTitle() {
  const response = await axios.get('https://jsonplaceholder.typicode.com/albums');
  return response.data[0].title;
}

module.exports = getFirstAlbumTitle;

Enter fullscreen mode

Exit fullscreen mode

…and here’s our initial mock-less test for this function, which verifies the function actually returns the title of the first album in the list:

// index.test.js
const getFirstAlbumTitle = require('./index');

it('returns the title of the first album', async () => {
  const title = await getFirstAlbumTitle();  // Run the function
  expect(title).toEqual('quidem molestiae enim');  // Make an assertion on the result
});

Enter fullscreen mode

Exit fullscreen mode

The test above does its job, but the test actually makes a network request to an API when it runs. This opens the test up to all sorts of false negatives if the API isn’t working exactly as expected (e.g. the list order changes, API is down, dev machine loses network connection, etc.). Not to mention, making these requests in a large number of tests can bring your test runs to a slow crawl.

But how can we change this? The API request is being made with axios as a part of getFirstAlbumTitle(). How in the world are we supposed to reach inside the function and change the behavior?

Mock it in 3 steps

Alright, here it is. This is the big secret that would have saved me mountains of time as I was wrestling with learning mocks. To mock an API call in a function, you just need to do these 3 steps:

1. Import the module you want to mock into your test file.
2. jest.mock() the module.
3. Use .mockResolvedValue(<mocked response>) to mock the response.

That’s it!

Here’s what our test looks like after doing this:

// index.test.js
const getFirstAlbumTitle = require('./index');
const axios = require('axios');

jest.mock('axios');

it('returns the title of the first album', async () => {
  axios.get.mockResolvedValue({
    data: [
      {
        userId: 1,
        id: 1,
        title: 'My First Album'
      },
      {
        userId: 1,
        id: 2,
        title: 'Album: The Sequel'
      }
    ]
  });

  const title = await getFirstAlbumTitle();
  expect(title).toEqual('My First Album');
});

Enter fullscreen mode

Exit fullscreen mode

What’s going on here?

Let’s break this down. The most important part to understand here is the import and jest.mock():

const axios = require('axios');

jest.mock('axios');

Enter fullscreen mode

Exit fullscreen mode

When you import a module into a test file, then call it in jest.mock(<module-name>), you have complete control over all functions from that module, even if they’re called inside another imported function. Immediately after calling jest.mock('axios'), Jest replaces every function in the axios module with empty «mock» functions that essentially do nothing and return undefined:

const axios = require('axios');
jest.mock('axios')

// Does nothing, then returns undefined:
axios.get('https://www.google.com')

// Does nothing, then returns undefined:
axios.post('https://jsonplaceholder.typicode.com/albums', {
    id: 3,
    title: 'Album with a Vengeance'
})

Enter fullscreen mode

Exit fullscreen mode

So now that you’ve eliminated the default behavior, you can replace it with your own…

  axios.get.mockResolvedValue({
    data: [
      {
        userId: 1,
        id: 1,
        title: 'My First Album'
      },
      {
        userId: 1,
        id: 2,
        title: 'Album: The Sequel'
      }
    ]
  });

Enter fullscreen mode

Exit fullscreen mode

The mocked replacement functions that Jest inserted into axios happen to come with a whole bunch of cool superpower methods to control their behavior! The most important one here, for the purposes of a simple beginner mock, is .mockResolvedValue(). When you call this on a mocked method, anything you pass in will be the default return value when the mocked function is called for the remainder of the test. Simply put: you can make axios.get() return whatever you want! And it doesn’t matter whether it’s called directly in your test file or as a part of a function imported into your test – Jest will mock the function no matter where it’s called!

Use this newfound power to give your functions exactly what they should expect from the API calls. Stop worrying about what the network requests return, and just focus on what YOUR code does once it gets the response!

If you want to play around with the examples, feel free to use this demo repository:

Wrapping up

There you have it! This is the very basics of what you need to mock functions from another module: import the module, jest.mock() the module, then insert your own return values with .mockResolvedValue()!

I recommend starting here, using only these techniques as you start building out your first mocks for your network calls. Once you have a foundational understanding of what’s going on here, you can slowly start adding the other robust mocking features included in Jest.

See also: Mocking Modules (Jest documentation).

EDIT: Also, be sure to clear your mocks between tests by running jest.resetAllMocks() after each test. This will help ensure your mocks won’t interfere with future tests. (Thanks for pointing this out, @mjeffe!)


Where to go from here

Alright, you’ve learned the basics of mocking and successfully implemented the strategies above in several tests. You can import and mock resolved values for all your API calls like an old pro. What’s next?

While the methods described above will cover most simple use cases, Jest has a lot of mocking functionality and methods to do some really powerful things. You can incrementally add some of the concepts below to super-charge your mocks:

  1. Check out the other mock function methods listed in the Jest docs: Mock Functions. You can use methods such as mockReturnedValue() to mock synchronous returns and mockResolvedValueOnce() to only return a value the first time it’s called.
  2. Want to see how many times a mocked function is called, what it was called with, and what it returned? Check out the mock.calls and mock.results properties (also in the Mock Functions documentation)
  3. Do you have your own custom functions that make network requests? You can mock your own modules too after they’re imported into the test file: jest.mock('./path/to/js/module/file')! Careful here though that you’re only mocking what’s necessary. Your tests should make sure your functions do what’s expected with a given mock input, and it can be easy to end up writing tests that instead just confirm you passed in mocked data.
  4. Want a function to act as it was originally written, but still want to see how many times it was called? Check out jest.spyOn().
  5. Find yourself mocking the same function over and over in multiple tests? Give it default mock responses in __mocks__ folders using Manual Mocks!

I hope this saves others some of the wasted time and frustration I went through! If anything doesn’t make sense here, please leave a comment and I’d be happy to try to answer any questions. Also, let me know if there’s anything else that helped you have an «Aha!» moment while learning to mock!


Did you find this this article useful? Feel free to subscribe to my articles below or follow me on Twitter for more developer tips and article announcements!


  • CSRF token
  • Usage
  • Mock Axios response in tests

    • Example
    • Mock poll requests in tests with Axios

We use Axios to communicate with the server in Vue applications and most new code.

In order to guarantee all defaults are set you should not use Axios directly, you should import Axios from axios_utils.

CSRF token

All our requests require a CSRF token.
To guarantee this token is set, we are importing Axios, setting the token, and exporting axios .

This exported module should be used instead of directly using Axios to ensure the token is set.

Usage

  import axios from './lib/utils/axios_utils';

  axios.get(url)
    .then((response) => {
      // `data` is the response that was provided by the server
      const data = response.data;

      // `headers` the headers that the server responded with
      // All header names are lower cased
      const paginationData = response.headers;
    })
    .catch(() => {
      //handle the error
    });

Mock Axios response in tests

To help us mock the responses we are using axios-mock-adapter.

Advantages over spyOn():

  • no need to create response objects
  • does not allow call through (which we want to avoid)
  • clear API to test error cases
  • provides replyOnce() to allow for different responses

We have also decided against using Axios interceptors because they are not suitable for mocking.

Example

  import axios from '~/lib/utils/axios_utils';
  import MockAdapter from 'axios-mock-adapter';

  let mock;
  beforeEach(() => {
    // This sets the mock adapter on the default instance
    mock = new MockAdapter(axios);
    // Mock any GET request to /users
    // arguments for reply are (status, data, headers)
    mock.onGet('/users').reply(200, {
      users: [
        { id: 1, name: 'John Smith' }
      ]
    });
  });

  afterEach(() => {
    mock.restore();
  });

Mock poll requests in tests with Axios

Because a polling function requires a header object, we need to always include an object as the third argument:

  mock.onGet('/users').reply(200, { foo: 'bar' }, {});

Понравилась статья? Поделить с друзьями:
  • Mobile friendly другая ошибка
  • Mobile crane error
  • Mitsubishi ошибка b1b06
  • Mitsubishi pajero sport ошибка p1766
  • Mitsubishi pajero sport ошибка p0421