I'm testing a login component that uses Axios. I tried mocking Axios with axios-mock-adapter
, but when I run the tests, it still errors out with:
Error: Request failed with status code 404
How do I properly mock Axios in my tests?
login.spec.js:
import Vue from 'vue'
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Login from '../../src/components/global/login/Login.vue';
import Raven from "raven-js";
import jQuery from 'jquery'
import Vuex from 'vuex'
import router from '../../src/router'
var axios = require('axios');
var MockAdapter = require('axios-mock-adapter');
describe('Login.vue', () => {
let wrapper;
let componentInstance;
let mock;
beforeEach(() => {
global.requestAnimationFrame = setImmediate,
mock = new MockAdapter(axios)
wrapper = shallowMount(Login, {
router,
$: jQuery,
attachToDocument: true,
mocks: {
$t: () => { },
Raven: Raven,
},
data() {
return {
email: '',
password: '',
}
}
})
componentInstance = wrapper.vm;
})
afterEach(() => {
mock.reset()
})
it('calls `axios()` with `endpoint`, `method` and `body`', async () => {
const formData = {
email: 'example@gmail.com',
password: '111111'
};
let fakeData = { data: "fake response" }
mock.onPost(`${process.env.VUE_APP_BASE_URL}/login/`, formData).reply(200, fakeData);
wrapper.vm.email = 'example@gmail.com';
wrapper.vm.password = '111111';
wrapper.vm.doSigninNormal()
})
})
Login.vue
doSigninNormal() {
const formData = {
email: this.email,
password: this.password
};
this.$v.$touch()
if (this.$v.$invalid ) {
this.loading = false;
this.emailLostFocus = true;
this.passwordLostFocus = true;
$('html, body').animate({scrollTop:110}, 'slow')
} else {
axios.post("/login", formData, {
headers: { "X-localization": localStorage.getItem("lan") }
})
.then(res => {
if (!res.data.result) {
if (res.data.errors) {
for (var i = 0; i < res.data.errors.length; i++) {
this.$toaster.error(res.data.errors[i].message);
if (
res.data.errors[0].message == "Your email is not yet verified"
) {
this.showVerificationLinkButton = true;
}
if (res.data.errors[i].field === "email") {
this.$toaster.error(res.data.errors[i].message);
}
if (res.data.errors[i].field === "password") {
this.$toaster.error(res.data.errors[i].message);
}
}
}
this.loading = false;
this.$v.$reset();
} else {
this.loading = false;
Raven.setUserContext({
email: res.data.user.email,
id: res.data.user.id
});
this.$store.dispatch("login", res);
this.$v.$reset();
}
})
.catch((err) => {
console.log('catch', err);
});
}
}
The issue is on the axios-mock-adapter package. It requires an instance of axios using the
.create()
method. See here: creating an instanceIn your App.js, use:
Nothing needs to be changed in the tests though.
I got the hint from tests of axios-mock-adapter.
An example of such is: post test
Mocking Axios:
There are two easy ways to mock axios so your tests don't perform real http requests and use a mock object instead:
set axios as a component property:
So you can inject a mock in your tests easily:
I think this is the most straightforward way.
Use a plugin to inject axios in every component
You can setup a plugin like vue-plugin-axios to inject axios automatically into every component, like:
Without the need to explicitly declare it in
data
.Then in your test, instead of passing the mock as part of
data
, you pass it as part ofmocks
, which is the wayvue-test-utils
deals with global injections:This is how to mock axios calls to prevent call real axios and perform real http request.
Configuring mock behavior and access call parameters
With
jest.fn
you can setup a mock function to return a specific object, like:const post = jest.fn( () => ({status: 200, response: ...}) )
You can also access the parameters to the call via
hasBeenCalledWith' method, or more complex stuff via
mock.calls` (more info here):expect(post).toHaveBeenCalledWith(expectedParams)
.So, your final test should be like the following I think:
If we send an request like:
The last http request will become
Because mock-adapter take over axios default adapter, apis that not match mock rules will be pass through, and be processed by default adapter , both these adapter select logic go through here(core/dispatchRequest.js):
Thus, if you use mock, please use full url starts with http://
Testing wrong login URL
The root problem is the test code sets up
axios-mock-adapter
on a different URL than actually used inLogin.vue
, so the request is not stubbed:The fix is to make the test code use the same URL (i.e.,
/login
):Need to await axios.post()
The unit test isn't awaiting the
POST
request, so the test wouldn't be able to reliably verify calls or responses (without a hack).The fix is to update
doSigninNormal()
to return theaxios.post()
promise to allow callers to await the result:Verifying login result
To verify the result, you could declare a local data prop to hold the login result 1️⃣, update
doSigninNormal()
to process the response (which is mocked withfakeData
in the test), capturing the result 2️⃣. Then, just check that data property after awaitingdoSignInNormal()
.