This question already has an answer here:
Test suite failed to run TypeError: Cannot set property 'content' of null Running in Jest
(1 answer)
Closed 1 year ago.
I am writing a test on a Vue project, and I am new to Framwork Jest and Vue testing utilities, have not found solutions to similar issues, have tried with multiple components but the error is still similar, I thought about Mock axios, it didn't work, if someone can help me I'm stuck,
if someone can offer me a solution or have an idea how it goes, it will be perfect
Api.js
import axios from 'axios'
import qs from 'qs';
import router from '#/router';
import defaultExport from '#/store';
let apiBaseUrl = document.querySelector('meta[name="apiBaseUrl"]');
if(!apiBaseUrl) {
apiBaseUrl.content = '/api/';
}
let newAxios = axios.create({
headers: {
// A fix for IE11 - we need to define Pragma header
Pragma: 'no-cache',
// 'X-Requested-With': 'XMLHttpRequest'
},
withCredentials: true,
//baseURL: apiBaseUrl.content,
paramsSerializer: function (params) {
return qs.stringify(params)
}
});
Login.vue
<script>
****
data() {
return {
currentMode: "login",
passwordForgotMode: false,
registerMode: false,
email: "",
password: "",
rememberMe: false,
emailRules: [
v => !!v || 'E-Mail wird benötigt',
],
passwordRules: [
v => !!v || 'Passwort wird benötigt',
],
valid: false,
}
},
computed: {
...mapGetters({
isAdministrator: 'account/isAdministrator',
})
},
methods: {
...mapActions({
handleLogin: 'account/handleLogin',
addSnackbarFromError: 'app/addSnackbarFromError',
}),
send() {
if (this.$refs.form.validate()) {
this.handleLogin({
rememberMe: this.rememberMe,
email: this.email,
password: this.password,
})
.then(() => {
window.localStorage.setItem('logged_in', true);
if (this.$route.query.redirect) {
this.$router.push(decodeURIComponent(this.$route.query.redirect));
} else {
if (this.$store.getters["account/isAdministrator"]) {
this.$router.push({name: 'userNotificationsOverview'});
} else {
this.$router.push({name: 'startingSite'});
}
}
})
.catch((error) => {
this.password = '';
this.addSnackbarFromError(error)
})
}
}
}
}
</script>
Test.test.js
import { shallowMount, createLocalVue } from '#vue/test-utils';
import Vuex from 'vuex';
import Login from "#pages/Login";
let wrapper;
let store;
let actions;
let mutations;
let state;
const localVue = createLocalVue();
localVue.use(Vuex);
beforeEach(() => {
actions = {
someAction: jest.fn()
};
mutations = {
someMutation: jest.fn()
};
state = {
key: {}
};
store = new Vuex.Store({
actions,
mutations,
state,
});
wrapper = shallowMount(Login, {
propsData: {},
attachTO: '#root',
mocks: {},
stubs: {},
methods: {},
store,
localVue,
});
});
afterEach(() => {
wrapper.destroy();
});
describe('Component', () => {
test('is a Vue instance', () => {
expect(wrapper.contains('h2')).toBe(true)
});
});
● Test suite failed to run
TypeError: Cannot set property 'content' of null
let apiBaseUrl = document.querySelector('meta[name="apiBaseUrl"]');
7 | if(!apiBaseUrl) {
> 8 | apiBaseUrl.content = '/api/';
| ^
9 | }
10 |
11 | let newAxios = axios.create({
The bug is here:
if(!apiBaseUrl) {
^^^^^^^^^^^
apiBaseUrl.content = '/api/';
}
The !apiBaseUrl condition checks that apiBaseUrl is falsy (null or undefined), and then tries to use it, leading to the error you observed.
Simply remove the ! operator from the condition to fix that error:
if(apiBaseUrl) {
apiBaseUrl.content = '/api/';
}
Related
I have the same issue than this post: Why does vitest mock not catch my axios get-requests?
I would like to test my vuex store on vuejs and it works for getters etc but not for actions part with axios get request.
I don't know if it's a good practice to test vuex store than the component in Vue ?
But I guess I need to test both, right ?
a project https://stackblitz.com/edit/vitest-dev-vitest-nyks4u?file=test%2Ftag.spec.js
my js file to test tag.js
import axios from "axios";
const actions = {
async fetchTags({ commit }) {
try {
const response = await axios.get(
CONST_CONFIG.VUE_APP_URLAPI + "tag?OrderBy=id&Skip=0&Take=100"
);
commit("setTags", response.data);
} catch (error) {
console.log(error);
return;
}
},
};
export default {
state,
getters,
actions,
mutations,
};
then my test (tag.spec.js)
import { expect } from "chai";
import { vi } from "vitest";
import axios from "axios";
vi.mock("axios", () => {
return {
default: {
get: vi.fn(),
},
};
});
describe("tag", () => {
test("actions - fetchTags", async () => {
const users = [
{ id: 1, name: "John" },
{ id: 2, name: "Andrew" },
];
axios.get.mockImplementation(() => Promise.resolve({ data: users }));
axios.get.mockResolvedValueOnce(users);
const commit = vi.fn();
await tag.actions.fetchTags({ commit });
expect(axios.get).toHaveBeenCalledTimes(1);
expect(commit).toHaveBeenCalledTimes(1);
});
});
It looks like some other peolpe have the same issues https://github.com/vitest-dev/vitest/issues/1274 but it's still not working.
I try with .ts too but I have exactly the same mistake:
FAIL tests/unit/store/apiObject/tag.spec.js > tag > actions - fetchTags
AssertionError: expected "spy" to be called 1 times
❯ tests/unit/store/apiObject/tag.spec.js:64:24
62| await tag.actions.fetchTags({ commit });
63|
64| expect(axios.get).toHaveBeenCalledTimes(1);
| ^
65| expect(commit).toHaveBeenCalledTimes(1);
66| });
Expected "1"
Received "0"
Thanks a lot for your help.
I finally found the mistake, it was on my vitest.config.ts file, I have to add my global config varaible for my api: import { config } from "#vue/test-utils";
import { defineConfig } from "vitest/config";
import { resolve } from "path";
var configApi = require("./public/config.js");
const { createVuePlugin } = require("vite-plugin-vue2");
const r = (p: string) => resolve(__dirname, p);
export default defineConfig({
test: {
globals: true,
environment: "jsdom",
},
define: {
CONST_CONFIG: configApi,
},
plugins: [createVuePlugin()],
resolve: {
alias: {
"#": r("."),
"~": r("."),
},
// alias: {
// "#": fileURLToPath(new URL("./src", import.meta.url)),
// },
},
});
I've recently learnt a bit about vuex and store.
I was about to use it for calling my api but it keeps saying my
url is undefined.
here is my vuex codes:
import { createStore } from 'vuex'
import axios from "axios";
const url = 'https://myurl'
export default createStore({
state: {
catList: [],
transactList: [],
user: [],
requestList: [],
catInList: [],
productList: [],
errorMs: '',
calling: false,
mobile: ''
},
getters: {
allUsers: (state) => state.user,
transactList: (state) => state.transactList,
categoryList: (state) => state.catList,
requestList: (state) => state.requestList,
productList: (state) => state.productList,
},
mutations: {
SET_Users (state, user) {
state.user = user
}
},
actions: {
checkAuth() {
const token = localStorage.getItem('token') ? localStorage.getItem('token') : ''
axios.defaults.baseURL = url
axios.defaults.headers.common['Authorization'] = token ? `Bearer ${token}` : ''
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
},
async axiosPost ({dispatch} ,{url}) {
dispatch('checkAuth')
await axios.post(url+'/login', {
mobile: this.mobile
}).then(response => {
this.calling = false
localStorage.setItem('token', response.data.token)
})
},
async axiosGet ({dispatch} , {url, formData}) {
dispatch('checkAuth')
await axios.get(url, formData).catch(err => {
console.log(err)
})
}
},
created() {
}
})
actually I wanted to define my api globally, so that I can use it for different components only by adding url + '/login' but I'm not sure why it keeps saying my url is not defined.
can anyone help me with the errors?
I am new to vue.js, I have a simple web application(Vue frontend connected to a FastAPI backend) that a user can create an account and login, All of this works so far but when I refresh the page the user is logged out.
And console show an error:
Uncaught (in promise) TypeError: Cannot read property '$store' of undefined
What am I doing wrong? How to keep user logged in even after page refresh. Can anyone please help me?? thanks
store/index.js
import Vuex from 'vuex';
import Vue from 'vue';
import createPersistedState from "vuex-persistedstate";
import auth from './modules/auth';
// Load Vuex
Vue.use(Vuex);
// Create store
const store = new Vuex.Store({
modules: {
auth
},
plugins: [createPersistedState()]
});
export default store
store/modules/auth.js
import { postUserLogInAPI } from "../../service/apis.js";
const state = {
token: "",
expiration: Date.now(),
username: ""
};
const getters = {
getToken: (state) => state.token,
getUsername: (state) => state.username,
getFullname: (state) => state.fullname,
isAuthenticated: (state) => state.token.length > 0 && state.expiration > Date.now()
};
const actions = {
async LogIn({ commit }, model) {
await postUserLogInAPI(model).then(function (response) {
if (response.status == 200) {
commit("LogIn", response.data)
}
})
},
async LogOut({ commit }) {
commit('LogOut')
}
};
const mutations = {
LogIn(state, data) {
state.username = data.username
state.fullname = data.fullname
state.token = data.token
state.expiration = new Date(data.expiration)
},
LogOut(state) {
state.username = ""
state.fullname = ""
state.token = ""
state.expiration = Date.now();
},
};
export default {
state,
getters,
actions,
mutations
};
service/http.js
import axios from 'axios'
import { Loading, Message } from 'element-ui'
import router from '../router/index.js'
import store from '../store';
let loading
function startLoading() {
loading = Loading.service({
lock: true,
text: 'Loading....',
background: 'rgba(0, 0, 0, 0.7)'
})
}
function endLoading() {
loading.close()
}
axios.defaults.withCredentials = true
axios.defaults.baseURL = 'http://0.0.0.0:80/';
axios.interceptors.request.use(
(confing) => {
startLoading()
if (store.getters.isAuthenticated) {
confing.headers.Authorization = "Bearer " + store.getters.getToken
}
return confing
},
(error) => {
return Promise.reject(error)
}
)
axios.interceptors.response.use(
(response) => {
endLoading()
return response
},
(error) => {
Message.error(error.response.data)
endLoading()
const { status } = error.response
if (status === 401) {
Message.error('Please Login')
this.$store.dispatch('LogOut')
router.push('/login')
}
return Promise.reject(error)
}
)
export default axios
components/NavBar.vue
<script>
export default {
name: "NavBar",
computed: {
isLoggedIn: function () {
return this.$store.getters.isAuthenticated;
},
username: function () {
return this.$store.getters.getUsername;
},
fullname: function () {
return this.$store.getters.getFullname;
},
},
methods: {
async logout() {
await this.$store.dispatch("LogOut");
this.$router.push("/login");
},
},
};
</script>
I want to test my Api functions which are on separate file outside vue component. Inside this methods i call api by Vue.axios, and i can't find the way to mock and test it like in this post:
How do I test axios in jest
example method:
cancelAuction: function (auction_id) {
if (validateApiInt(auction_id)) {
return Vue.axios.delete(`/auctions/${auction_id}`);
}
return {};
},
example usage:
const response = await AuctionApi.cancelAuction(id);
Ok that was pretty obvious. I had to mock whole Vue like below:
jest.mock('vue', () => ({
axios: {
get: jest.fn()
},
}));
Just start learning Jest + #vue/test-utils. Here is a simple example for those people want to mock "vue-axios".
// #/components/Helloword.vue
<template>
<div>
<h1>Email: <span>{{ email }}</span></h1>
<button #click="fetchData">Get Random Email</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
email: '',
};
},
methods: {
async fetchData() {
const res = (await this.axios.get('https://randomuser.me/api/')).data
.results[0].email;
this.email = res;
},
},
};
</script>
--
// test/unit/example.spec.js
import { mount } from '#vue/test-utils';
import HelloWorld from '#/components/HelloWorld.vue';
import axios from 'axios';
jest.mock('axios', () => ({
get: () =>
Promise.resolve({
data: {
results: [{ email: 'mockAxios#email.com' }],
},
}),
}));
describe('HelloWorld.vue', () => {
it('click and fetch data...', async (done) => {
const wrapper = mount(HelloWorld, {
mocks: {
axios,
},
});
await wrapper.find('button').trigger('click');
wrapper.vm.$nextTick(() => {
expect(wrapper.find('h1').text()).toContain('#');
done();
});
});
});
I am trying to make an application which servers api through django rest framework and the frontend is done in vue.js2.
I am referring this blog to help me authenticating jwt via axios. I also tried this one when I couldn't make the first one run correctly
Here is my settings.py file
INSTALLED_APPS = [
...
'rest_framework',
'rest_framework.authtoken',
'admindash',
'corsheaders',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
...
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
# Password validation
# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAdminUser',
),
'TEST_REQUEST_RENDERER_CLASSES': (
'rest_framework.renderers.MultiPartRenderer',
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.TemplateHTMLRenderer'
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 20,
}
JWT_AUTH = {
'JWT_ALLOW_REFRESH': True,
'JWT_EXPIRATION_DELTA': timedelta(hours=1),
'JWT_REFRESH_EXPIRATION_DELTA': timedelta(days=7),
}
#Cors origin
CORS_ORIGIN_WHITELIST = (
'localhost:8080'
)
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True
Here is my vue store
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = 'X-CSRFToken'
export default new Vuex.Store({
state: {
authUser: {},
isAuthenticated: false,
jwt: localStorage.getItem('token'),
endpoints: {
obtainJWT: 'http://127.0.0.1:8000/api/v1/auth/obtain_token/',
refreshJWT: 'http://127.0.0.1:8000/api/v1/auth/refresh_token/',
baseUrl: 'http://127.0.0.1:8000/api/v1'
}
},
mutations: {
setAuthUser(state, {
authUser,
isAuthenticated
}) {
Vue.set(state, 'authUser', authUser)
Vue.set(state, 'isAuthenticated', isAuthenticated)
},
updateToken(state, newToken) {
localStorage.setItem('token', newToken);
state.jwt = newToken;
},
removeToken(state) {
localStorage.removeItem('token');
state.jwt = null;
}
},
actions: {
obtainToken(context, {username, password}) {
const payload = {
username: username,
password: password
}
const headers= {
'Content-Type': 'application/json'
}
axios.post(this.state.endpoints.obtainJWT, headers, payload)
.then((response) => {
this.commit('updateToken', response.data.token);
console.log(this.state.jwt);
})
.catch((error) => {
console.log(error);
})
},
refreshToken () {
const payload = {
token: this.state.jwt
}
axios.post(this.state.endpoints.refreshJWT, payload)
.then((response) => {
this.commit('updateToken', response.data.token);
console.log(this.state.jwt)
})
.catch((error) => {
console.log(error)
})
}
}
})
and here is login.vue
<script>
import axios from 'axios'
import FormInput from './FormInput'
export default {
name: 'Login',
components: {
FormInput
},
data () {
return {
username: '',
password: ''
}
},
computed: {
/* ...mapState([
'jwt',
'endpoints'
]) */
},
methods: {
/* ...mapActions([
'obtainToken'
]), */
authenticateBeforeSubmit () {
this.$store.dispatch('obtainToken', {
username: this.username,
password: this.password
}).then(() => {
this.$router.push('/')
}).catch((error) => {
console.log(error)
})
/* const payload = {
username: this.username,
password: this.password
}
axios.post(this.$store.state.endpoints.obtainJWT, payload)
.then((response) => {
this.$store.commit('updateToken', response.data.token)
console.log(this.$store.state.jwt);
const base = {
baseUrl: this.$store.state.endpoints.baseUrl,
headers: {
Authorization: `JWT ${this.$store.state.jwt}`,
'Content-Type': 'application/json'
},
xhrFields: {
withCredentials: true
}
}
const axiosInstance = axios.create(base)
axiosInstance({
url: "/user/",
method: "get",
params: {}
})
.then((response) => {
this.$store.commit("setAuthUser",
{authUser: response.data, isAuthenticated: true}
)
this.$router.push({name: 'Home'})
})
})
.catch((error) => {
console.log(error);
console.debug(error);
console.dir(error);
}) */
}
}
}
</script>
Now the problem is I am getting two errors
Just as I load login view in browser, i get this error
Uncaught (in promise) TypeError: Cannot read property 'protocol' of undefined
at isURLSameOrigin (VM34519 isURLSameOrigin.js:57)
at dispatchXhrRequest (VM34513 xhr.js:109)
at new Promise ()
at xhrAdapter (VM34513 xhr.js:12)
at dispatchRequest (VM34521 dispatchRequest.js:59)
isURLSameOrigin # VM34519 isURLSameOrigin.js:57
dispatchXhrRequest # VM34513 xhr.js:109
xhrAdapter # VM34513 xhr.js:12
dispatchRequest # VM34521 dispatchRequest.js:59
18:29:09.976
I don't have slightest idea what this error is about, I searched it and i didn't find anything that works
This I get when I click submit which fires authenticateBeforeSubmit method
Uncaught TypeError: Cannot read property 'dispatch' of undefined
at VueComponent.authenticateBeforeSubmit (VM34576 Login.vue:68)
at invoker (VM34494 vue.esm.js:2026)
at HTMLButtonElement.fn._withTask.fn._withTask (VM34494 vue.esm.js:1825)
authenticateBeforeSubmit # VM34576 Login.vue:68
invoker # VM34494 vue.esm.js:2026
fn._withTask.fn._withTask # VM34494 vue.esm.js:1825
18:29:30.912
What i understand it is saying that i am calling dispatch on action incorrectly but i don't get how to make it work
As I click submit it "redirects" to
http://127.0.0.1:8080/login?username=f1uk3r&password=thisissparta
but it doesn't recieves any token
I have tested token using
curl -X POST -H "Content-Type: application/json" -d '{"username":"f1uk3r","password":"thisissparta"}' http://127.0.0.1:8000/api/v1/auth/obtain_token/
and it gives me a token so there shouldn't be any problem in django part I think. What am I doing wrong, how can I rectify it.
I figured it out, so answering for future reference and if anybody else finds this useful
TypeError: Cannot read property 'protocol' of undefined at isURLSameOrigin
this can be solved by importing axios and VueAxios correctly
I initialized it like this
import Vue from 'vue'
import VueAxios from 'vue-axios'
import Vuex from 'vuex'
import axios from 'axios'
Vue.use(Vuex);
Vue.use(axios, VueAxios);
While it should have been initialized like this
import Vue from 'vue'
import VueAxios from 'vue-axios'
import Vuex from 'vuex'
import axios from 'axios'
Vue.use(Vuex);
Vue.use(VueAxios, axios);
Cannot read property 'dispatch' of undefined at VueComponent
Again I wasn't initializing store correctly so i made a directory in "src" directory named it "store" and in this directory I made a file named "store.js"
Then in main.js import store and initialize in the instance
import store from './store/store.js';
Vue.use(VeeValidate);
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})