155

I have this action in React:

export function fetchPosts() {
    const request = axios.get(`${WORDPRESS_URL}`);
    return {
        type: FETCH_POSTS,
        payload: request
    }
}

How do I test Axios in this case?

Jest has this use case on their site for asynchronous code where they use a mock function, but can I do this with Axios?

Reference: An Async Example

I have done this so far to test that it is returning the correct type:

it('should dispatch actions with the correct type', () => {
    store.dispatch(fetchPosts());
    let action = store.getActions();
    expect(action[0].type).toBe(FETCH_POSTS);
});

How can I pass in mock data and test that it returns?

frederj
  • 1,483
  • 9
  • 20
Adear
  • 1,885
  • 2
  • 11
  • 18

7 Answers7

242

Without using any other libraries:

import * as axios from "axios";

// Mock out all top level functions, such as get, put, delete and post:
jest.mock("axios");

// ...

test("good response", () => {
  axios.get.mockImplementation(() => Promise.resolve({ data: {...} }));
  // ...
});

test("bad response", () => {
  axios.get.mockImplementation(() => Promise.reject({ ... }));
  // ...
});

It is possible to specify the response code:

axios.get.mockImplementation(() => Promise.resolve({ status: 200, data: {...} }));

It is possible to change the mock based on the parameters:

axios.get.mockImplementation((url) => {
    if (url === 'www.example.com') {
        return Promise.resolve({ data: {...} });
    } else {
        //...
    }
});

Jest v23 introduced some syntactic sugar for mocking Promises:

axios.get.mockImplementation(() => Promise.resolve({ data: {...} }));

It can be simplified to

axios.get.mockResolvedValue({ data: {...} });

There is also an equivalent for rejected promises: mockRejectedValue.

Further Reading:

A Jar of Clay
  • 5,622
  • 6
  • 25
  • 39
  • Can I also define a mockImplementation for e.g. `axios({ method: 'get', ... })` ? Or do I need to refactor to `axios.get( ... )` instead ? – kca Aug 01 '22 at 10:19
  • @kca You should be fine doing it that way, feel free to try it and comment your results here! – A Jar of Clay Aug 19 '22 at 13:51
  • The question is HOW (if possible) can I define a mockImplementation for `axios` directly, because that is not a method, but the root object, and can't be done with `jest.mock("axios")`. I would need to `jest.mock("parent-of-axios")`. – kca Sep 05 '22 at 06:18
  • Try `axios.mockImplementation(({ method }) => { if (method === 'get' { /* */ } })`, and/or feel free to ask a question about this and link it here :) – A Jar of Clay Sep 05 '22 at 08:02
  • This has nothing to do with the question though does it? This would mock axios in the context of the test file but, when you run the `fetchPosts` method from the question, the context of the fetchPosts method would be the module and not the test file. Thus, the function would pull a reference to the import of axios defined in the module. Not the overridden version that exists in the testing scope. – Cody Pace Jun 05 '23 at 19:08
103

I used axios-mock-adapter. In this case the service is described in ./chatbot. In the mock adapter you specify what to return when the API endpoint is consumed.

import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import chatbot from './chatbot';

describe('Chatbot', () => {
    it('returns data when sendMessage is called', done => {
        var mock = new MockAdapter(axios);
        const data = { response: true };
        mock.onGet('https://us-central1-hutoma-backend.cloudfunctions.net/chat').reply(200, data);

        chatbot.sendMessage(0, 'any').then(response => {
            expect(response).toEqual(data);
            done();
        });
    });
});

You can see it the whole example here:

Service: https://github.com/lnolazco/hutoma-test/blob/master/src/services/chatbot.js

Test: https://github.com/lnolazco/hutoma-test/blob/master/src/services/chatbot.test.js

Mohammad Kermani
  • 5,188
  • 7
  • 37
  • 61
Luis Nolazco
  • 1,272
  • 1
  • 9
  • 7
43

I could do that following the steps:

  1. Create a folder __mocks__/ (as pointed by @Januartha comment)
  2. Implement an axios.js mock file
  3. Use my implemented module on test

The mock will happen automatically

Example of the mock module:

module.exports = {
    get: jest.fn((url) => {
        if (url === '/something') {
            return Promise.resolve({
                data: 'data'
            });
        }
    }),
    post: jest.fn((url) => {
        if (url === '/something') {
            return Promise.resolve({
                data: 'data'
            });
        }
        if (url === '/something2') {
            return Promise.resolve({
                data: 'data2'
            });
        }
    }),
    create: jest.fn(function () {
        return this;
    })
};
9

I've done this with nock, like so:

import nock from 'nock'
import axios from 'axios'
import httpAdapter from 'axios/lib/adapters/http'

axios.defaults.adapter = httpAdapter

describe('foo', () => {
    it('bar', () => {
        nock('https://example.com:443')
            .get('/example')
            .reply(200, 'some payload')

        // test...
    })
})
Jon B
  • 51,025
  • 31
  • 133
  • 161
  • 2
    I did try this but i seems that axios does not play well with nock.https://github.com/node-nock/nock/issues/699 but thank you for your help none the less – Adear Jul 13 '17 at 10:40
  • 2
    nock is the best way to handle http calls in tests – pravindot17 May 25 '21 at 08:14
9

Look at this

  1. The function to test album.js
const fetchAlbum = function () {
 return axios
   .get("https://jsonplaceholder.typicode.com/albums/2")
   .then((response) => {
     return response.data;
   });
};
  1. The test album.test.js
const axios = require("axios");
const { fetchAlbum } = require("../utils.js");

jest.mock("axios");

test("mock axios get function", async () => {
    expect.assertions(1);
    const album = {
      userId: 1,
      id: 2,
      title: "sunt qui excepturi placeat culpa",
    };
    const payload = { data: album };
    // Now mock axios get method
    axios.get = jest.fn().mockResolvedValue(payload);
    await expect(fetchAlbum()).resolves.toEqual(album);
  });
STREET MONEY
  • 534
  • 4
  • 8
2

New tools for testing have been introduced since the question was initially answered.

The problem with mocking is that you often test the mock and not the real context of your code, leaving some areas of this context untested. An improvement over telling axios what promise to return is intercepting http requests via Service Workers.

Service worker is a client-side programmable proxy between your web app and the outside world. So instead of mocking promise resolution it is a more broader solution to mock the proxy server itself, intercepting requests to be tested. Since the interception happens on the network level, your application knows nothing about the mocking.

You can use msw (Mock Service Worker) library to do just that. Here is a short video explaining how it works.

The most basic setup I can think of is this: 1️⃣ set up handlers, which are similar to express.js routing methods; 2️⃣ set up mock server and pass handlers as it’s arguments; 3️⃣ configure tests to so that mock server will intercept our requests; 4️⃣ perform tests; 5️⃣ close mock server.

Say you want to test the following feature:

import axios from "axios";

export const fetchPosts = async () => {
  const request = await axios.get("/some/endpoint/");
  return {
    payload: request,
  };
};

Then test could look like this:

import { rest } from "msw";
import { setupServer } from "msw/node";
import fetchPosts from "./somewhere";

// handlers are usually saved in separate file(s) in one  destined place of the app,
// so that you don't have to search for them when the endpoints have changed
const handlers = [ 1️⃣
  rest.get("/some/endpoint/", (req, res, ctx) =>
    res(ctx.json({ message: "success" }))
  ),
];

const server = setupServer(...handlers); 2️⃣

beforeAll(() => {
  server.listen(); 3️⃣
});

describe("fetchPosts", () => {
  it("should return 'success' message", async () => {
    const resp = await fetchPosts();
    expect(resp.payload?.data?.message).toEqual("success"); 4️⃣
  });
});

afterAll(() => {
  server.close(); 5️⃣
});

The configuration may be different depending on framework you are using. Some general examples for, among others, React (both REST and GraphQL) and Angular can be found on MSW’ repo. A Vue example is provided by VueMastery. You can also find examples on MSW' recipes page.

Pawel Kam
  • 1,684
  • 3
  • 14
  • 30
1

For those looking to use axios-mock-adapter in place of the mockfetch example in the Redux documentation for async testing, I successfully used the following:

File actions.test.js:

describe('SignInUser', () => {
  var history = {
    push: function(str) {
        expect(str).toEqual('/feed');
    }
  }

  it('Dispatches authorization', () => {
    let mock = new MockAdapter(axios);
    mock.onPost(`${ROOT_URL}/auth/signin`, {
        email: 'test@test.com',
        password: 'test'
    }).reply(200, {token: 'testToken' });

    const expectedActions = [ { type: types.AUTH_USER } ];
    const store = mockStore({ auth: [] });

    return store.dispatch(actions.signInUser({
        email: 'test@test.com',
        password: 'test',
      }, history)).then(() => {
        expect(store.getActions()).toEqual(expectedActions);
  });

});

In order to test a successful case for signInUser in file actions/index.js:

export const signInUser = ({ email, password }, history) => async dispatch => {
  const res = await axios.post(`${ROOT_URL}/auth/signin`, { email, password })
    .catch(({ response: { data } }) => {
        ...
  });

  if (res) {
    dispatch({ type: AUTH_USER });                 // Test verified this
    localStorage.setItem('token', res.data.token); // Test mocked this
    history.push('/feed');                         // Test mocked this
  }
}

Given that this is being done with jest, the localstorage call had to be mocked. This was in file src/setupTests.js:

const localStorageMock = {
  removeItem: jest.fn(),
  getItem: jest.fn(),
  setItem: jest.fn(),
  clear: jest.fn()
};
global.localStorage = localStorageMock;
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
vapurrmaid
  • 2,287
  • 2
  • 14
  • 30
  • How did you use the localStorageMock – Kermit_ice_tea Feb 14 '18 at 21:54
  • @Kermit_ice_tea `src/setupTests.js` is documented in create-react-app as a global setup for jest/enzyme testing. In that file I created an object that I arbitrarily called `localStorageMock` with dummy functions (getItem, setItem). The magic is at the bottom where I set `global.localStorage` equal to this object. I could've done this in one line instead of naming the object localStorageMock. The purpose of this entire setup was to simply avoid any code dealing with localstorage from breaking during tests. – vapurrmaid Feb 14 '18 at 23:51