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?
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: '[email protected]',
password: '111111'
};
let fakeData = { data: "fake response" }
mock.onPost(`${process.env.VUE_APP_BASE_URL}/login/`, formData).reply(200, fakeData);
wrapper.vm.email = '[email protected]';
wrapper.vm.password = '111111';
wrapper.vm.doSigninNormal()
})
})
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 root problem is the test code sets up axios-mock-adapter
on a different URL than actually used in Login.vue
, so the request is not stubbed:
// login.spec.js:
mock.onPost(`${process.env.VUE_APP_BASE_URL}/login/`, formData).reply(200, fakeData)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Login.vue
axios.post("/login", formData)
^^^^^^
The fix is to make the test code use the same URL (i.e., /login
):
// login.spec.js
mock.onPost("/login", formData).reply(200, fakeData)
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 the axios.post()
promise to allow callers to await the result:
// Login.vue
doSigninNormal() {
return axios.post(...)
}
// login.spec.js
await wrapper.vm.doSigninNormal()
expect(mock.history.post.length).toBe(1)
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 with fakeData
in the test), capturing the result 2️⃣. Then, just check that data property after awaiting doSignInNormal()
.
// Login.vue
data() {
return {
...
result: '' 1️⃣
}
}
methods: {
doSignInNormal() {
return axios.post(...)
.then(resp => this.result = resp.data.result) 2️⃣
}
}
// login.spec.js
const result = await wrapper.vm.doSigninNormal()
expect(result).toBe(fakeData.result)
expect(wrapper.vm.result).toBe(fakeData.result)
There are two easy ways to mock axios so your tests don't perform real http requests and use a mock object instead:
import axios from 'axios`;
Vue.component({
data() {
return {
axios,
}
},
methods: {
makeApiCall() {
return this.axios.post(...)
}
}
})
So you can inject a mock in your tests easily:
it('test axions', function() {
const post = jest.fn();
const mock = {
post,
}
// given
const wrapper = shallowMount(myComponent, {
data: {
axios: mock,
}
});
// when
wrapper.vm.makeApiCall();
// then
expect(post).toHaveBeenCalled();
});
I think this is the most straightforward way.
You can setup a plugin like vue-plugin-axios to inject axios automatically into every component, like:
makeApiCall(){
this.$axios.post(...)
}
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 of mocks
, which is the way vue-test-utils
deals with global injections:
it('test axions', function() {
const post = jest.fn();
const mock = {
post,
}
// given
const wrapper = shallowMount(myComponent, {
mocks: {
$axios: mock,
}
});
// when
wrapper.vm.makeApiCall();
// then
expect(post).toHaveBeenCalled();
});
This is how to mock axios calls to prevent call real axios and perform real http request.
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:
it('calls axios() with endpoint, method and body',async (done) => {
// given
const formData = { email: '[email protected]', password: '111111' };
const fakeResponse = {response: "fake response"};
const email = '[email protected]';
const uri = 'somepath/login/'; // I dont think you can access Vue process env variables in the tests, so you'll need to hardcode.
const password = '11111';
const post = jest.fn(() => Promise.resolve({status: 200}) );
const mock = {
post,
}
const wrapper = shallowMount(Component, {
data() {
return {
axios: mock,
// email,
// password, // you could do this instead to write to wrapper.vm later
}
}
});
wrapper.vm.email = '[email protected]';
wrapper.vm.password = '111111';
// when
await wrapper.vm.doSigninNormal();
// then
expect(post).toHaveBeenCalledWith({uri, password, email});
// or
const calls = post.mock.calls;
const firstParam = calls[0][0];
expect(firstParam.uri).toBe(uri);
expect(firstParam.email).toBe(email);
expect(firstParam.password).toBe(password);
done();
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With