Jest + Vue test utlis TypeError: Cannot set property 'content' of null [duplicate] - vue.js

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

Why Vitest mock Axios doesn't work on vuex store testing?

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)),
// },
},
});

vuex "url" of undefined using axios

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?

How to keep user logged in between page refreshes in FastAPI and Vue

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>

How to mock VueAxios in jest

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();
});
});
});

Not able to access token using axios and djangorestframework-jwt

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/>'
})