In my Vue/Nuxt application, I have a plugin with the following code:
import Vue from 'vue';
import axios from 'axios';
axios.interceptors.response.use(res => {
console.log('Interceptor response: ', res.data);
return res;
},
err => {
if (err.response.status === 401) {
this.$store.dispatch(AUTH_LOGOUT);
redirect('/');
}
});
Vue.use(axios);
The problem is that both 'redirect' and 'this.$store' appear to be undefined. Can you help me with how I can use redirect or store inside this vue/nuxt plugin?
You can use context variable.
Example:
plugins/axios.js
import axios from 'axios'
export default function (context, inject) {
axios.interceptors.response.use(res => {
console.log('Interceptor response: ', res.data);
return res;
}, err => {
if (err.response.status === 401) {
context.redirect('/');
context.store.dispatch(AUTH_LOGOUT);
}
});
inject('axios', axios)
}
Inject in $root & context
Related
I've been following this guide to making a global loader component but there's an issue with setting up the interceptors with the config.
Error: TypeError: Cannot read properties of undefined (reading 'config')
I'm using vue 3.2.2, vuex 4.0.2, axios 0.21, and Laravel 8
The loader component is not working and I suspect it might be due to the config error.
app.js
<script>
import { createApp } from 'vue'
import { createStore } from 'vuex'
import router from './router';
import App from './components/App';
import { loader } from "./loader";
const store = createStore({
state() {
},
modules: {
loader,
}
})
const app = createApp(App)
app.use(router)
app.use(store)
app.mount('#app')
</script>
App.vue
<script>
import Sidebar from "../components/Sidebar";
import {mapState} from "vuex/dist/vuex.mjs";
import Loader from "./Loader";
import axios from "axios";
axios.defaults.showLoader = true;
export default {
components: {
Sidebar,
Loader
},
computed: {
...mapState('loader', ['loading'])
},
created() {
axios.interceptors.request.use(
config => {
if (config.showLoader) {
store.dispatch('loader/pending');
}
return config;
},
error => {
if (error.config.showLoader) {
store.dispatch('loader/done');
}
return Promise.reject(error);
}
);
axios.interceptors.response.use(
response => {
if (response.config.showLoader) {
store.dispatch('loader/done');
}
return response;
},
error => {
let response = error.response;
if (response.config.showLoader) {
store.dispatch('loader/done');
}
return Promise.reject(error);
}
)
}
}
</script>
Your interceptor arrow function having issues with config param
axios.interceptors.request.use((config) => {
if (config.showLoader) {
store.dispatch('loader/pending');
}
return config;
}, (error) => {
if (error.config.showLoader) {
store.dispatch('loader/done');
}
return Promise.reject(error);
});
axios.interceptors.response.use((response) => {
if (response.config.showLoader) {
store.dispatch('loader/done');
}
return response;
}, (error) => {
let response = error.response;
if (response.config.showLoader) {
store.dispatch('loader/done');
}
return Promise.reject(error);
})
Hello I'm using this axios wrapper in my project ,it's working as expected.But I want to use hooks in this class so I must change this into a functional component.Any ideas to change this into a functional component.Thanks...
import React from 'react';
import axios from 'axios';
import createAuthRefreshInterceptor from 'axios-auth-refresh';
const axiosApiInstance = axios.create({baseURL: "http://10.0.2.2:5001"});
const refreshAuthLogic = (failedRequest) => {
const options = {
method: 'post',
url: 'http://10.0.2.2:5001/api/token/refresh',
data: {
email: 'rwar#gmail.com',
refreshToken: 'testrefreshtoken'
},
};
return axios(options, {
pauseInstanceWhileRefreshing: true,
}).then((tokenRefreshResponse) => {
failedRequest.response.config.headers['Authorization'] =
'Bearer ' + tokenRefreshResponse.data.result.token;
return Promise.resolve();
});
};
createAuthRefreshInterceptor(axiosApiInstance, refreshAuthLogic);
axiosApiInstance.interceptors.response.use(
(response) => {
return response;
},
(error) => {
if (error && error.response && error.response.status === 401) {
// 401 error redirect to login
return Promise.reject(error);
}
if (error.response.status !== 401) {
return new Promise((resolve, reject) => {
reject(error);
});
}
},
);
export default axiosApiInstance;
I have seen similar questions but they dont actually address what am looking for.
so am using using axios globally in app.js for my vue app like window.axios=require('axios')
then in auth.js i have this
export function login(credentials){
return new Promise((res,rej) => {
axios.post('/api/auth/login', credentials)
.then((response) => {
res(response.data);
})
.catch((err) => {
rej("Wrong email or password");
})
});
}
which works fine on the login page
however in my test script
jest.mock("axios", () => ({
post: jest.fn(() => Promise.resolve({data:{first_name:'James','last_name':'Nwachukwu','token':'abc123'}}))
}));
import axios from 'axios'
import {login} from '../helpers/auth'
it("it logs in when data is passed", async () => {
const email='super#gmail.com'
const password='secret';
const result=await login({email,password});
expect(axios.post).toBeCalledWith('/api/auth/login',{"email": "super#gmail.com", "password": "secret"})
expect(result).toEqual({first_name:'James','last_name':'Nwachukwu','token':'abc123'})
})
shows axios is not defined
but if i change auth.js to
import axios from 'axios'
export function login(credentials){
return new Promise((res,rej) => {
axios.post('/api/auth/login', credentials)
.then((response) => {
res(response.data);
})
.catch((err) => {
rej("Wrong email or password");
})
});
}
test passes. how do i run the test without having to import axios on each vue file
I had the same problem just now. I am also including axios via window.axios = require('axios'); in my app.js.
The solution is to set your axios mock on window.axios in your test. So instead of this (incorrect):
axios = {
post: jest.fn().mockName('axiosPost')
}
const wrapper = mount(Component, {
mocks: {
axios: axios
}
})
When your component code calls axios.whatever it is really calling window.axios.whatever (as I understand it), so you need to mirror that in your test environment:
window.axios = {
post: jest.fn().mockName('axiosPost')
}
const wrapper = mount(Component, {
mocks: {
axios: window.axios
}
})
And in your test:
expect(window.axios.post).toHaveBeenCalled()
The above method works fine until you want to chain then to it. In which case you need to set your mock up like this:
window.axios = {
get: jest.fn(() => {
return {
then: jest.fn(() => 'your faked response')
}
}),
}
You don't need to pass it into the component mock though, you can just mount (or shallowMount) the component as usual
I want to test a vuex module called user.
Initially, I successfully registered my module to Vuex. Its works as expected.
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
user
}
})
export default store
My user module is defined as follows
store/modules/user.js
const state = {
token: getToken() || '',
}
export const getters = {
token: state => state.token,
}
const mutations = {
[SET_TOKEN]: (state, token) => {
state.token = token
}
}
const actions = {
[LOGIN] ({ commit }, body) {
return new Promise((resolve, reject) => {
login(body).then(response => { //login is an api method, I'm using axios to call it.
const { token } = response.data
setToken(token)
commit(SET_TOKEN, token)
resolve()
}).catch(error => {
reject(error)
})
})
}
}
export default {
state,
getters,
mutations,
actions
}
login api
api/auth.js
import request from '#/utils/request'
export function login (data) {
return request({
url: '/auth/login',
method: 'post',
data
})
}
axios request file
utils/request
import axios from 'axios'
import store from '#/store'
import { getToken } from '#/utils/auth'
const request = axios.create({
baseURL: process.env.VUE_APP_BASE_API_URL,
timeout: 5000
})
request.interceptors.request.use(
config => {
const token = getToken()
if (token) {
config.headers['Authentication'] = token
}
return config
}
)
export default request
When I want to write some test (using Jest), for example login action as shown above.
// user.spec.js
import { createLocalVue } from '#vue/test-utils'
import Vuex from 'vuex'
import actions from '#/store/modules/user'
const localVue = createLocalVue()
localVue.use(Vuex)
test('huhu', () => {
expect(true).toBe(true)
// implementation..
})
How can I write test for my Login action? Thanks. Sorry for my beginner question.
EDIT: SOLVED Thank you Raynhour for showing to me right direction :)
import { LOGIN } from '#/store/action.types'
import { SET_TOKEN } from '#/store/mutation.types'
import { actions } from '#/store/modules/user'
import flushPromises from 'flush-promises'
jest.mock('#/router')
jest.mock('#/api/auth.js', () => {
return {
login: jest.fn().mockResolvedValue({ data: { token: 'token' } })
}
})
describe('actions', () => {
test('login olduktan sonra tokeni başarıyla attı mı?', async () => {
const context = {
commit: jest.fn()
}
const body = {
login: 'login',
password: 'password'
}
actions[LOGIN](context, body)
await flushPromises()
expect(context.commit).toHaveBeenCalledWith(SET_TOKEN, 'token')
})
})
Store it's just a javascript file that will export an object. Not need to use vue test util.
import actions from '../actions'
import flushPromises from 'flush-promises'
jest.mock('../api/auth.js', () => {
return {
login: jest.fn()..mockResolvedValue('token')
}; // mocking API.
describe('actions', () => {
test('login should set token', async () => {
const context = {
commit: jest.fn()
}
const body = {
login: 'login',
password: 'password'
}
actions.login(context, body)
await flushPromises() // Flush all pending resolved promise handlers
expect(context.commit).toHaveBeenCalledWith('set_token', 'token')
})
})
but you need to remember that in unit tests all asynchronous requests must be mocked(with jest.mock or something else)
I would like to use $nuxt.$loading https://nuxtjs.org/api/configuration-loading/ outside of Vue component. I created central js for hitting APIs.
services/api-client.js
import axios from "axios";
import { state } from '../store/modules/sessions';
const axiosClient = axios.create({
baseURL: process.env.BASE_URL,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-Api-Key': state().token
}
});
axiosClient.interceptors.request.use(function (config) {
// show nuxt loading here
return config;
}, function (error) {
return Promise.reject(error);
});
axiosClient.interceptors.response.use(function (response) {
// hide nuxt loading here
if (response.data.status.code != 200) {
throw { message: response.data.status.errorDetail };
} else {
return response;
}
}, function (error) {
// hide nuxt loading here
return Promise.reject(error);
});
export default {
all(path) {
return axiosClient.get(path);
},
show(path) {
return this.all(path);
},
create(path, params) {
return axiosClient.post(path, params);
},
update(path, params) {
return axiosClient.put(path, params);
}
};
and from my index.vue I'm dispatching the actions which trigger the Api Request.
<template>
<div>
<h1> Welcome </h1>
</div>
</template>
<script>
export default {
created() {
this.$store.dispatch('getInsiders', this);
}
}
</script>
The solution to your problem is this code below.
Please try this.
export default function({ $axios, store }: any) {
$axios.onRequest((config: any) => {
store._vm.$nextTick(() => {
store._vm.$nuxt.$loading.start()
return config
})
})
$axios.onResponse((response: any) => {
store._vm.$nextTick(() => {
store._vm.$nuxt.$loading.finish()
return response
})
})
$axios.onError((error: any) => {
store._vm.$nextTick(() => {
store._vm.$nuxt.$loading.finish()
return Promise.reject(error)
})
})
}
Do you really need to declare own axios client?
Standard way how to do this is using nuxt's axios module and then customize it in your plugin.
nuxt.config.js
modules: ['#nuxtjs/axios'],
plugins: ['~/plugins/axios']
~/plugins/axios
export default ({ $axios, redirect }) => {
$axios.onError(error => {
// do anything you need
})
}
The axios module will manage loading status automatically.
Although you still can disable progress for individual requests
Eg from component/action
await this.$axios.$get('/myapi', { progress: false })