axios-module icon indicating copy to clipboard operation
axios-module copied to clipboard

mock up $axios

Open kyzia551 opened this issue 6 years ago • 31 comments

Hi! I'm trying to test vuex actions like

  async addEmail ({commit}, email) {
    commit('ADD_EMAIL_START')
    try {
      const resp = await this.$axios.$post('/users/addEmail', qs.stringify({ email }))
      commit('ADD_EMAIL_SUCCESSFUL')
    } catch (e) {
      commit('ADD_EMAIL_FAILED', e)
    }
  }

How to mock up $axios this way?

Stack: jest + vue-test-utils Thank you!

This feature request is available on Nuxt.js community (#c91)

kyzia551 avatar Feb 16 '18 16:02 kyzia551

@kyzia551 have you found any solution?

@pi0 docs still do not cover testing at all. I can improve them after I will get over this problem.

sobolevn avatar Apr 16 '18 09:04 sobolevn

I didn't dig into this yet. Contributions to the Docs and Code are more than welcome BTW ;)

pi0 avatar Apr 16 '18 09:04 pi0

I'm facing the same problem and I have a question regarding that: Is it really related specifically to the axios module or would one be facing the same problem with other modules/plugins? Whenever I import my store into the tests, this is refering to my actions object (which makes sense) and thus I don't have any modules/plugins injected into the $root element available.

Fubinator avatar Apr 18 '18 15:04 Fubinator

I came up with this solution: https://github.com/wemake-services/wemake-vue-template/blob/feature-nuxt/template/tests/unit/pages/index.spec.js#L55

import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'

it('should load new comments on actions', async () => {
    expect.assertions(3)
    const wrapper = mount(Index, { store, localVue, propsData })
    wrapper.vm.$axios = axios

    const mock = new MockAdapter(wrapper.vm.$axios)
    mock.onGet('/comments').reply(200, [mockedComment])

    await wrapper.vm.$store.dispatch('fetchComments', wrapper.vm)
    expect(wrapper.vm.$store.state.comments.length).toBe(1)
    expect(wrapper.vm.$store.state.comments[0].email).toBe(mockedComment.email)

    wrapper.vm.$nextTick(() => {
      expect(wrapper.findAll('.comment__component').length).toBe(1)
    })
})

sobolevn avatar Apr 18 '18 15:04 sobolevn

I've played a bit around with your solution @sobolevn I ended up "injecting" axios into the store instance so it is available when using this. Maybe this is going to help someone with my specific problem.


test.beforeEach(() => {
  store = createStore({
    actions,
    getters,
    state,
    mutations
  }, {
    mockState
  })
  store.$axios = axios;
})

test('foo', async t => {
  const wrapper = mount(Index, {
    store,
    localVue
  });
  const mock = new MockAdapter(store.$axios);
  mock.onGet('/foo/1').reply(200, mockState.active);

  await wrapper.vm.$store.dispatch('foo', 1);
  
  // assertions...
})

Fubinator avatar Apr 19 '18 10:04 Fubinator

to unit test an action which use this.$axios, the trick is to bind your axios mock to the action function

store file:

// store/index.js

export default {
  actions: {
    async getResults (context, payload) {
      return await this.$axios.$get('/api/something');
    },
  },
};

store test file:

// store/__tests__/index.test.js

import store from '../index.js';
import axios from 'axios';

let mockAxiosGetResult;
jest.mock('axios', () => ({
  $get: jest.fn(() => Promise.resolve(mockAxiosGetResult)),
}));

let action;
const testedAction = (context = {}, payload = {}) => {
  return store.actions[action].bind({$axios: axios})(context, payload);
};

describe(`store`, () => {
  describe(`actions`, () => {
    describe(action = 'getResults', () => {
      it(`returns an empty array if axios returns an empty array`, async (done) => {
        mockAxiosGetResult = [];
        expect(await testedAction()).toEqual([]);
        done();
      });
    });
  });
});

Oliboy50 avatar May 10 '18 15:05 Oliboy50

I don't want to use jest, any alternatives?

vinayakkulkarni avatar May 10 '19 10:05 vinayakkulkarni

I am testing vuex store and for API call I have used @nuxtjs/$axios in an actual store So while executing test case it's throwing error this.$axios.$get is not a function any update on this

jaydadhaniya avatar Sep 16 '19 11:09 jaydadhaniya

@jaydadhaniya : https://github.com/vinayakkulkarni/nuxt-ava-e2e-unit-testing

Maybe that'll help?

vinayakkulkarni avatar Oct 02 '19 13:10 vinayakkulkarni

I found also this example from which I could inspire and have a working test case for my vuex action which is using @nuxtjs/$axios

Nuxt.js + Jest

It's also using the trick to bind your axios mock to the action function as proposed by @Oliboy50

pascal-reuse avatar Oct 02 '19 13:10 pascal-reuse

@pi0 I think this can be closed

ricardogobbosouza avatar Oct 11 '19 11:10 ricardogobbosouza

@ricardogobbosouza no, we still need a doc entry.

manniL avatar Oct 11 '19 12:10 manniL

Still don`t get how to mock this.$axios.get() even without using store, e.g. when you call it somewhere in mounted() or any method

stasoft91 avatar Dec 19 '19 10:12 stasoft91

Why it doesn't work ?

export const actions = {
  async getLocales({commit}) {
    const { locales } = await this.$axios.$get('/api/getlocales');

    if (locales) {
      commit('setLocales', locales);
    }
  }
};
import {actions} from '../../../store/lang';
import axios from 'axios';

let mockAxiosGetResult;

jest.mock('axios', () => ({
  $get: jest.fn(() => { console.log('1122'); return Promise.resolve('1111')})
}));

let action;

const testedAction = (context = {}, payload = {}) => {
  return actions[action].bind({$axios: axios})(context, payload);
};

describe(`store`, () => {
  describe(`actions`, () => {
    describe(action = 'getLocales', () => {
      it(`returns an empty array if axios returns an empty array`, async (done) => {
        mockAxiosGetResult = [];
        expect(await testedAction()).toEqual('1111');
        done();
      });
    });
  });
});

Error

    Expected: "1111"
    Received: undefined

golubvladimir avatar Jan 27 '20 14:01 golubvladimir

My solution:

import {actions} from './../store/index';
import axios from 'axios';
import { Server } from "miragejs"

axios.$get = async (url) => {
  const result = await axios.get(url);
  return result.data;
};

let action;
let server;

beforeEach(() => {
  server = new Server({
    routes() {
      this.get("/langs", () => ({
        locales: ['en', 'ru']
      }))
    },
  })
});

afterEach(() => {
  server.shutdown();
});

const testedAction = (context = {}, payload = {}) => {
  return actions[action].bind({$axios: axios})(context, payload);
};

describe(`store`, () => {
  describe(`actions`, () => {
    describe(action = 'getLangs', () => {
      it(`returns an empty array if axios returns an empty array`, async (done) => {
        expect(await testedAction()).toEqual({locales: ['en', 'ru']});
        done();
      });
    });
  });
});

golubvladimir avatar Jan 28 '20 08:01 golubvladimir

With commit: https://github.com/golubvladimir/nuxt-vuex-jest

golubvladimir avatar Jan 28 '20 08:01 golubvladimir

I found that by binding, but not in a function, as suggested in the comments above, i call the action immediately, which is why it is wrapped in a function, to not do that. But what i need, and maybe others too, is not to call an action directly, but trigger something (a button etc) that ultimately calls the action. Im kind of new to coding, so I looked to see what this is inside the actions file, and its the store, the same store I create inside the test (for most this was obvious). So, my simple solution was, after mocking axios

jest.mock('axios', () => ({
   get: jest.fn(() => {}),
   post: jest.fn(() => Promise.resolve({})),
}))

to do store.$axios = axios and everything works!

airBogdan avatar Mar 04 '20 20:03 airBogdan

I came across a fairly simple solution that uses the mocks option for the mount and shallowMount functions. This option can mock anything that is globally injected onto the Vue instance, like the $axios module.

Here is an example with Sinon stubs. Say I wanted to mock the axios $get function:

const stub = sinon.stub()

mount(Component, {
  mocks: {
    $axios: {
      $get: stub
    }
  }
})

Any calls to this.$axios.$get in the component will run the stub instead. You can spy on this stub or use it to return fake values.

I'm not as familiar with Jest, but I think it would look something like this:

const mock = jest.fn()

mount(Component, {
  mocks: {
    $axios: {
      $get: mock
    }
  }
})

AbleLincoln avatar May 21 '20 00:05 AbleLincoln

Finally solved it this way:

store.js:

export const actions = {
  // Populate the state using an API request via axios
  async populateEmotions ({ commit, state }) {
    try {
      const response = await this.$axios.get('http://localhost:3000/api/emotions')

      if (response.status === 200) {
        const emotions = await response.data
        // console.log(emotions)
        commit('populate', emotions)
      } else {
        console.log(response)
        throw new Error('Non-200 result received')
      }
    } catch (e) {
      console.error(e)
    }
  }
}

test.spec.js:

import axios from 'axios'
import state from './stateX'
import { getters, actions } from '@/store/index.js'

// Create a mocked store with the imported getters and actions to test
const store = {
  state,
  getters,
  actions
}

// Convert the axios library into a jest mock
jest.mock('axios')

// Add the mocked axios to our mocked store so the tested code can call: this.$axios.get()
store.actions.$axios = axios

// Mock the commit function so we can check it is called
const commit = jest.fn()


describe('In the Store:', () => {
  describe('The Actions...', () => {

    test('populateEmotions() calls the commit function, passing the emotions from the axios get request', async () => {
      // Set the mock so that this.$axios.get() returns a Resolved value with status 200, and the mocked state.emotions
      store.actions.$axios.get.mockResolvedValue({
        status: 200,
        data: state.emotions
      })

      // Test the populateEmotions() action
      await store.actions.populateEmotions({
        commit,
        state
      })

      // Expect the commit function to have been called appropriately
      expect(commit).toHaveBeenCalledWith('populate', state.emotions)
    })
  })
})

narkan avatar Jun 21 '20 19:06 narkan

So, finally found time to share my solution. Here we are testing nuxt`s page with asyncData and specified id.

import { shallowMount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import vuetify from 'vuetify'
import MockAdapter from 'axios-mock-adapter'
import axios from 'axios'
import { createStore } from '~/.nuxt/store'
import _id from '~/pages/_id'
import MAIN_URL from '~/constants/MAIN_URL'
import TESTDATA from '~/test/utils/TESTDATA'

const mock = new MockAdapter(axios)
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(vuetify)

const TEST_ID = 4

describe('Main page', () => {
  let store

  beforeEach(() => {
    store = createStore()
    mock.onGet(`${MAIN_URL}/api/page/${TEST_ID}`).reply(200, TESTDATA)
  })

  test('is a Vue instance', async () => {
    const $route = { params: { id: TEST_ID } }
    const route = $route
    let wrapper = shallowMount(_id, {
      store,
      localVue,
      mocks: {
        content: [],
        $route,
        route,
        $axios: axios
      }
    })

    expect(wrapper.isVueInstance()).toBeTruthy()

    expect((await wrapper.vm.$options.asyncData(wrapper.vm)).content).toEqual(
      TESTDATA.content
    )

    // Init page with mocked asyncData (such kludge because there is no native asyncData support in jest)
    wrapper.vm.$axios = axios
    wrapper = shallowMount(_id, {
      store,
      localVue,
      mocks: {
        content: await wrapper.vm.$options.asyncData(wrapper.vm),
        $route,
        route,
        $axios: axios
      }
    })
    expect(wrapper.isVueInstance()).toBeTruthy()
  })
})

stasoft91 avatar Jun 30 '20 14:06 stasoft91

All of the solutions above helped me reach this solution that worked and was simple enough for me to wrap my head around:

import { actions } from '@/store/modules/greetings'

describe('greetings store actions', () => {
  it('addGreeting', async (done) => {
    const message = "arbitrary text";
    const greeting = {
      type: "greeting",
      attributes: {
        message: message
      }
    }
    const mockResult = {
      data: {
        data: greeting
      }
    };
    const commit = jest.fn();
    const mockAxios = {
      post: jest.fn(() => Promise.resolve(mockResult))
    };
    actions._vm = {
      $axios: mockAxios
    };

    await actions.addGreeting(
      {commit},
      {message: message}
    );

    expect(mockAxios["post"]).toHaveBeenCalledWith(
      "greetings/",
      { data: greeting }
    );
    expect(commit).toHaveBeenCalledWith("addGreeting", greeting);
    done()
  });
});

Hope this is helpful to others 🍀

zhao-li avatar Jul 25 '20 06:07 zhao-li

Mocking out commit and other mutations seem a bit weird since they are part of the same store class. Here's an implementation that tests actions not as separate functions. It was much easier to get this working once testing actions as functions was working.

import store from "@/store/index.js";

describe('greetings store actions', () => {
  it('addGreeting', async (done) => {
    const message = "arbitrary text";
    const greeting = {
      type: "greeting",
      attributes: {
        message: message
      }
    }
    const mockResult = {
      data: {
        data: greeting
      }
    };
    let mockAxios = {
      post: jest.fn(() => Promise.resolve(mockResult))
    };
    store._vm.$axios = mockAxios;

    await store.dispatch("greetings/addGreeting", {message: message});
    expect(store.state.greetings["greetings"]).toEqual([greeting]);
    done()
  });
});

Hopefully, that can save someone else some time.

zhao-li avatar Jul 25 '20 07:07 zhao-li

Still don`t get how to mock this.$axios.get() even without using the store, e.g. when you call it somewhere in mounted() or any method

Have you find any solution for it ..because I also stuck here., this.$axios.get, -> can not read get of undefined

vipulphysiofirst avatar Sep 09 '20 08:09 vipulphysiofirst

I am using nuxt + jest for unit testing, when id testing a page it has Axios call in a method like this
IN pay.vue

async pay(){
   try{
     var respone  = this.$axios.get('API_URL_HERE);
     this.fee = response.data;
   } catch(e){
      console.log(e)
   }
 }

and test file pay.spec.js

import { mount } from '@vue/test-utils';
import paynow from '../pages/pay';
import   axios from "axios";
import Vue from 'vue'

jest.mock("axios", () => ({
  get: () => Promise.resolve({ data: [{ val: 1 }] })
}));
 
describe("paynow.vue", () => {
  it("mocking the axios call to get posts should work", async () => {
    var wrapper = mount(paynow);
    wrapper.vm.pay() 
  });
});

But I am getting this error after running it. TypeError: Cannot read property 'get' of undefined Axios is injected in the plugin then why it is showing undefined

vipulphysiofirst avatar Sep 09 '20 08:09 vipulphysiofirst

Did anyone come up with a solution on how to mock $axios when used inside mounted method?

arunans23 avatar Dec 12 '20 10:12 arunans23

Here's my solution, I hope it helps someone.

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

describe('Test Middleware', () => {
  const localVue = createLocalVue();

  // Add axios to the store object
  Vuex.Store.prototype.$axios = axios;

  localVue.use(Vuex);

});

03balogun avatar Mar 12 '21 22:03 03balogun

Is there any official documentation for this module related to testing or using this module with jest or ava? I don't see it in the documentation of nuxtjs or this module.

rlam3 avatar May 04 '21 20:05 rlam3

same problem as everyone here how do you test a vuex action that is making use of the axios module?

simonsankar avatar Jun 17 '21 19:06 simonsankar

I came up with this solution: https://github.com/wemake-services/wemake-vue-template/blob/feature-nuxt/template/tests/unit/pages/index.spec.js#L55

import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'

it('should load new comments on actions', async () => {
    expect.assertions(3)
    const wrapper = mount(Index, { store, localVue, propsData })
    wrapper.vm.$axios = axios

    const mock = new MockAdapter(wrapper.vm.$axios)
    mock.onGet('/comments').reply(200, [mockedComment])

    await wrapper.vm.$store.dispatch('fetchComments', wrapper.vm)
    expect(wrapper.vm.$store.state.comments.length).toBe(1)
    expect(wrapper.vm.$store.state.comments[0].email).toBe(mockedComment.email)

    wrapper.vm.$nextTick(() => {
      expect(wrapper.findAll('.comment__component').length).toBe(1)
    })
})

I know this is a really old issue, but just wanted to point out that ur comment helped me by letting me know of axios-mock-adapter which is a nice package, but this solution does not go well with TS and nuxt's axios plugin. the $ helpers will be missing and TS would complain about NuxtAxiosInstance and AxiosInstance.

I've seen in the thread some devs manually add the $ helpers into the axios instance that is used in the mock adapter, i guess it does the job but i don't know about how hacky it is. Still a bit easier than mocking $axios from the ground up though.

Just-Hussain avatar Jul 11 '21 09:07 Just-Hussain

Found a solution for mocking $axios if you are using composition-api! I'm using testing-library/vue but this can work with @vue/test-utils as well.

import axios from 'axios'

render(Component, {
      mocks: { 
        $nuxt: {
          context: {
            $axios: axios
          },
        },
      }, 
    })

jesus4497 avatar May 27 '22 03:05 jesus4497