Axios catch error Request failed with status code 404
The issue is on the axios-mock-adapter package. It requires an instance of axios using the .create()
method.
See here:
creating an instance
In your App.js, use:
import axios from "axios";
const instance = axios.create();
instance.post("http://localhost/api/user/update", {name: "Test"}, {headers: {"Authorization": "Bearer token")}});
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:
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.
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:
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.
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:
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();
});
Testing wrong login URL
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)
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 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)
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 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)
If axios instance adapter(xhr or http) taked over by axios-mock-adapter, there will have an error with bad baseURL config like this:
{baseURL:'/for/bar'}
If we send an request like:
get('/api/v1/exampleService')
The last http request will become
'http://host:port/for/bar/for/bar/api/v1/exampleService'
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):
if (config.baseURL && !isAbsoluteURL(config.url)) {
config.url = combineURLs(config.baseURL, config.url);
}
Thus, if you use mock, please use full url starts with http://