axios-module
axios-module copied to clipboard
mock up $axios
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!
@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.
I didn't dig into this yet. Contributions to the Docs and Code are more than welcome BTW ;)
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.
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'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...
})
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();
});
});
});
});
I don't want to use jest
, any alternatives?
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 : https://github.com/vinayakkulkarni/nuxt-ava-e2e-unit-testing
Maybe that'll help?
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
It's also using the trick to bind your axios mock to the action function as proposed by @Oliboy50
@pi0 I think this can be closed
@ricardogobbosouza no, we still need a doc entry.
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
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
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();
});
});
});
});
With commit: https://github.com/golubvladimir/nuxt-vuex-jest
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!
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
}
}
})
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)
})
})
})
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()
})
})
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 🍀
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.
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
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
Did anyone come up with a solution on how to mock $axios when used inside mounted method?
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);
});
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.
same problem as everyone here how do you test a vuex action that is making use of the axios module?
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.
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
},
},
},
})