40

I'm using jest and axios-mock-adapter to test axios API calls in redux async action creators.

I can't make them work when I'm using a axios instance that was created with axios.create() as such:

import axios from 'axios';

const { REACT_APP_BASE_URL } = process.env;

export const ajax = axios.create({
  baseURL: REACT_APP_BASE_URL,
});

which I would consume it in my async action creator like:

import { ajax } from '../../api/Ajax'

export function reportGet(data) {
  return async (dispatch, getState) => {
    dispatch({ type: REQUEST_TRANSACTION_DATA })

    try {
      const result = await ajax.post(
         END_POINT_MERCHANT_TRANSACTIONS_GET,
         data,
      )
      dispatch({ type: RECEIVE_TRANSACTION_DATA, data: result.data })
      return result.data
    } catch (e) {
      throw new Error(e);
    }
  }
}

Here is my test file:

import {
  reportGet,
  REQUEST_TRANSACTION_DATA,
  RECEIVE_TRANSACTION_DATA,
} from '../redux/TransactionRedux'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import { END_POINT_MERCHANT_TRANSACTIONS_GET } from 'src/utils/apiHandler'
import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'

const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
const store = mockStore({ transactions: {} })

test('get report data', async () => {
  let mock = new MockAdapter(axios)

  const mockData = {
    totalSalesAmount: 0
  }

  mock.onPost(END_POINT_MERCHANT_TRANSACTIONS_GET).reply(200, mockData)

  const expectedActions = [
    { type: REQUEST_TRANSACTION_DATA },
    { type: RECEIVE_TRANSACTION_DATA, data: mockData },
  ]

  await store.dispatch(reportGet())
  expect(store.getActions()).toEqual(expectedActions)
})

And I only get one action Received: [{"type": "REQUEST_TRANSACTION_DATA"}] because there was an error with the ajax.post.

I have tried many ways to mock the axios.create to no avail without really knowing what I'm doing..Any Help is appreciated.

UnstableEagle
  • 562
  • 2
  • 16
kyw
  • 6,685
  • 8
  • 47
  • 59

7 Answers7

83

OK I got it. Here is how I fixed it! I ended up doing without any mocking libraries for axios!

Create a mock for axios in src/__mocks__:

// src/__mocks__/axios.ts

const mockAxios = jest.genMockFromModule('axios')

// this is the key to fix the axios.create() undefined error!
mockAxios.create = jest.fn(() => mockAxios)

export default mockAxios

Then in your test file, the gist would look like:

import mockAxios from 'axios'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'

// for some reason i need this to fix reducer keys undefined errors..
jest.mock('../../store/rootStore.ts')

// you need the 'async'!
test('Retrieve transaction data based on a date range', async () => {
  const middlewares = [thunk]
  const mockStore = configureMockStore(middlewares)
  const store = mockStore()

  const mockData = {
    'data': 123
  }

  /** 
   *  SETUP
   *  This is where you override the 'post' method of your mocked axios and return
   *  mocked data in an appropriate data structure-- {data: YOUR_DATA} -- which
   *  mirrors the actual API call, in this case, the 'reportGet'
   */
  mockAxios.post.mockImplementationOnce(() =>
    Promise.resolve({ data: mockData }),
  )

  const expectedActions = [
    { type: REQUEST_TRANSACTION_DATA },
    { type: RECEIVE_TRANSACTION_DATA, data: mockData },
  ]

  // work
  await store.dispatch(reportGet())

  // assertions / expects
  expect(store.getActions()).toEqual(expectedActions)
  expect(mockAxios.post).toHaveBeenCalledTimes(1)
})
UnstableEagle
  • 562
  • 2
  • 16
kyw
  • 6,685
  • 8
  • 47
  • 59
  • 1
    You are my hero :) – Hasanavi Jul 08 '19 at 16:54
  • I know this is somewhat older, but you need to return the Promise.resolve ;) – Reinier Kaper Jan 14 '20 at 13:57
  • 2
    @ReinierKaper pretty sure the return is implicit in that arrow function pattern :) – kyw Feb 25 '20 at 01:05
  • I've been beating my head against the wall trying to solve this problem. I tried your solution but it didn't work. Can you take a look at my particular issue? Thanks! https://stackoverflow.com/questions/60745903/mock-custom-instance-of-axios-in-vue-jest-test – r_zelazny Mar 20 '20 at 14:10
  • 2
    it is recommended to create \_\_mocks\_\_ directory at the same level whit node_modules, more definition [here](https://jestjs.io/docs/en/manual-mocks#mocking-node-modules) – peja Apr 30 '20 at 09:33
  • This wasn't entirely my solution but it put me on the right path. I am newbie so maybe I am missing something, but 'src/__mocks__/axios.ts' wasn't auto-loading. I had to import .. '../__mocks__/..' – terary Dec 08 '20 at 06:08
  • 8
    Thanks! this helped a lot. I had to add `` otherwise `mockAxios` was being flagged as `unknown Type`: ` const mockAxios = jest.genMockFromModule('axios'); mockAxios.create = jest.fn(() => mockAxios); ` – Vikram Aug 06 '21 at 13:50
  • if you mock in this way, how you going to test axios header? – Alan Yong Oct 26 '21 at 08:07
  • how do you test a negative scenario now by doing Promise.reject({status: 401})? how can you reset module level mock ? – Murali N Nov 05 '21 at 20:46
16

If you need to create Jest test which mocks the axios with create in a specific test (and don't need the mock axios for all test cases, as mentioned in other answers) you could also use:

const axios = require("axios");

jest.mock("axios");

beforeAll(() => {
    axios.create.mockReturnThis();
});

test('should fetch users', () => {
    const users = [{name: 'Bob'}];
    const resp = {data: users};
    axios.get.mockResolvedValue(resp);

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

    return Users.all().then(data => expect(data).toEqual(users));
});

Here is the link to the same example of Axios mocking in Jest without create. The difference is to add axios.create.mockReturnThis()

Saša
  • 653
  • 1
  • 5
  • 17
  • 2
    Trying this, I get `TypeError: axios.create.mockReturnThis is not a function` – mikemaccana Aug 20 '21 at 15:47
  • 3
    Works perfectly as long as you aren't invoking axios.create before the mock can run. Very nice, thank you. @mikemaccana I would make sure you are properly mocking axios in that case, shown in the second line – light24bulbs Oct 14 '21 at 01:50
  • 4
    @mikemaccana if you use with Typescript you need to provide `axios as jest.Mocked` – callmenikk Nov 15 '22 at 13:21
  • Thanks! This works for me, which I love because there's no need of adding items in __mocks__ folder or anything else. You, sir, have my upvote. – Puce May 03 '23 at 13:56
  • I get typescript error `Cannot read properties of undefined (reading 'interceptors')`... `requestInstance.interceptors.request.use(async: (request: any) => { return request; );` – Fiddle Freak Jul 24 '23 at 05:09
6

here is my mock for axios

export default {
    defaults:{
        headers:{
            common:{
                "Content-Type":"",
                "Authorization":""
            }
        }
  },
  get: jest.fn(() => Promise.resolve({ data: {} })),
  post: jest.fn(() => Promise.resolve({ data: {} })),
  put: jest.fn(() => Promise.resolve({ data: {} })),
  delete: jest.fn(() => Promise.resolve({ data: {} })),
  create: jest.fn(function () {
      return {
          interceptors:{
              request : {  
                  use: jest.fn(() => Promise.resolve({ data: {} })),
              }
          },

          defaults:{
                headers:{
                    common:{
                        "Content-Type":"",
                        "Authorization":""
                    }
                }
          },
          get: jest.fn(() => Promise.resolve({ data: {} })),
          post: jest.fn(() => Promise.resolve({ data: {} })),
          put: jest.fn(() => Promise.resolve({ data: {} })),
          delete: jest.fn(() => Promise.resolve({ data: {} })),
      }
  }),
};
  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jan 05 '22 at 11:27
3

In your mockAdapter, you're mocking the wrong instance. You should have mocked ajax instead. like this, const mock = MockAdapter(ajax) This is because you are now not mocking the axios instance but rather the ajax because it's the one you're using to send the request, ie, you created an axios instance called ajax when you did export const ajax = axios.create...so since you're doing const result = await ajax.post in your code, its that ajax instance of axios that should be mocked, not axios in that case.

Richard
  • 412
  • 6
  • 9
1

I have another solution.

import {
  reportGet,
  REQUEST_TRANSACTION_DATA,
  RECEIVE_TRANSACTION_DATA,
} from '../redux/TransactionRedux'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import { END_POINT_MERCHANT_TRANSACTIONS_GET } from 'src/utils/apiHandler'
// import axios from 'axios'
import { ajax } from '../../api/Ajax' // axios instance
import MockAdapter from 'axios-mock-adapter'

const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
const store = mockStore({ transactions: {} })

test('get report data', async () => {
  // let mock = new MockAdapter(axios)
  let mock = new MockAdapter(ajax) // this here need to mock axios instance

  const mockData = {
    totalSalesAmount: 0
  }

  mock.onPost(END_POINT_MERCHANT_TRANSACTIONS_GET).reply(200, mockData)

  const expectedActions = [
    { type: REQUEST_TRANSACTION_DATA },
    { type: RECEIVE_TRANSACTION_DATA, data: mockData },
  ]

  await store.dispatch(reportGet())
  expect(store.getActions()).toEqual(expectedActions)
})

LPZ
  • 71
  • 3
0

another method: add this file to src/__mocks__ folder

import { AxiosStatic } from 'axios';

const axiosMock = jest.createMockFromModule<AxiosStatic>('axios');
axiosMock.create = jest.fn(() => axiosMock);

export default axiosMock;
Sam
  • 111
  • 1
  • 5
-1

The following code works!

   jest.mock("axios", () => {
        return {
            create: jest.fn(() => axios),
            post: jest.fn(() => Promise.resolve()),
        };
    });