问题描述
我正在将vue-test-utils和jest用于TDD。 我已经将$ store导入vuejs路由文件(routes.js)中,并出于某些目的使用store。 我已经为登录页面编写了一个简单的单元测试,但是当我运行测试时,开玩笑会在甚至没有一个测试者运行之前抛出以下错误。
错误:
TypeError: Cannot read property 'state' of undefined
130 | isAuth: true,> 131 | layout: $store.state.dom.isMobile ? mobileSinglePage : panel
| ^
route.js:
import $store from "../store"
// --------- layouts
import panel from "../layout/panel";
import mobileSinglePage from "../layout/mobileSinglePage";
import MainLayout from "../layout/index";
import auth from "../layout/auth"
// transactions
import _transaction from "../pages/transaction/_transaction"
const routes = [
{
path: "",component: MainLayout,children: [
{
path: "",redirect: "/panel/dashboard",},{
path: "login",component: login,name: "login",Meta: {
preventLoggedIn: true,layout: auth
}
},{
path: "/panel/transactions/:url",name: "_transactions",component: _transaction,Meta: {
isAuth: true,layout: $store.state.dom.isMobile ? mobileSinglePage : panel
}
},{
path: "*",name: 'error_page',redirect: '/404'
}
],];
export default routes;
login.spec.js
import { mount,createLocalVue } from "@vue/test-utils"
import login from '@/pages/auth/login'
import Vuex from "vuex"
const localVue = createLocalVue()
localVue.use(Vuex)
describe("login",() => {
it("simple test",() => {
const wrapper = mount(login,{
localVue,data(){
return {
form: {
mobile_number: '',}
},})
wrapper.find('#login').text()
})
})
Login.vue
<template>
<div class="w-100">
<transition>
<form @submit.prevent="requestOtp" class="w-100" v-if="step === 1">
<MobileInput
ref="mobile_number"
v-model="form.mobile_number"
autocomplete="false"
outlined
:counter="false"
label="mobile number"
/>
<AppButton
:disabled="!form.mobile_number || form.mobile_number.length !== 11"
type="submit"
color="secondary"
large
:loading="loading"
class="w-100 fontMobileMedium font0.875 loginBtn">
login
</AppButton>
</form>
</transition>
<transition>
<form @submit.prevent="verifyOtp" v-if="step === 2">
<AppPinCode
autofocus
dir="ltr"
:disabled="loading"
:length="6"
class="mb-6"
@complete="verifyOtp"
v-model="form.otpCode"/>
<AppButton
type="submit"
:loading="loading"
:disabled="!form.otpCode"
color="secondary"
large
class="w-100 fontMobileMedium font0.875 ">
login
</AppButton>
</form>
</transition>
</div>
</template>
<script>
import {mapActions} from "vuex"
import MobileInput from "../../components/inputs/MobileInput";
import AppPinCode from "../../components/inputs/AppPinCode";
import Mobile from "../../mixins/Mobile";
import {_setAuthData} from "../../services/utils"
export default {
name: "Login",mixins: [Mobile],data() {
return {
...mapActions('main',['crud']),form: {
mobile_number: '',device_info: {
hardware: "web",device_id: ""
},otpId: null,password: null,otpCode: '',step: 1,countDownTimer: null,loading: false,showPass: false,interValTime: null,}
},components: {
MobileInput,AppPinCode
},methods: {
async requestOtp() {
if (!this.form.mobile_number)
return
try {
this.loading = true
const otp = await this.crud({
action: 'post',section: 'auth/otp',data: {mobile_number: this.normalizeMobile(this.form.mobile_number)},auth: false,showError: true,})
this.form.otpId = otp.data.id
} catch (e) {
this.step = 2
console.log(e)
} finally {
this.loading = false
}
},async verifyOtp() {
if (!this.form.otpCode || !this.form.otpId)
return
try {
this.loading = true
const data = {
mobile_number: this.normalizeMobile(this.form.mobile_number),otp: this.form.otpCode
}
const verify = await this.crud({
action: 'patch',section: `auth/otp/${this.form.otpId}`,data,})
const {auth} = verify.data
await this.finalLogin(auth)
} catch (e) {
this.form.otpCode = ''
console.log(e)
} finally {
this.loading = false
}
},async finalLogin(userData) {
const {access_token,refresh_token,expires_in,user} = userData
await _setAuthData({
access_token,profile: user
},true)
this.$router.push({name: 'dashboard'})
},}
</script>
store / dom.js(商店模块)
import breakPoints from "../assets/scss/breakpoints.scss"
export default {
namespaced: true,state: {
isRtl: true,isMobile: false,isTablet: false,breakpoints: {
sm: breakPoints.minSm.split("px")[0],md: breakPoints.minMd.split("px")[0],lg: breakPoints.minLg.split("px")[0],xl: breakPoints.minXl.split("px")[0],sizetoHideSidebar: breakPoints.minLg.split("px")[0],mobileSize: breakPoints.minSm.split("px")[0],tabletSize: breakPoints.minMd.split("px")[0],AppWindowWidth: window.innerWidth,AppWindowHeight: window.innerHeight,mutations: {
SET(state,{key,value}) {
state[key] = value
},DELETE(state,type = 'string'}) {
switch (type) {
case 'array':
state[key] = []
break
case 'object':
state[key] = {}
break
case 'boolean':
state[key] = false
break
default:
state[key] = null
}
},APPEND(state,value}) {
// console.log(state)
if (typeof state[key] !== 'object')
return false
if (Array.isArray(state[key])) {
// console.log('also here')
state[key].push(value)
}
if (state[key] && !Array.isArray(state[key]) && state[key] !== null) {
// console.log(value)
state[key] = {...state[key],...value}
// console.log(state)
}
}
},actions: {
deviceDetectBySize({commit,state}) {
const width = window.innerWidth
commit('SET',{key: 'isTablet',value: width <= state.tabletSize})
commit('SET',{key: 'isMobile',value: width <= state.mobileSize})
},windowSizefn({dispatch}) {
dispatch('deviceDetectBySize')
window.addEventListener('resize',function (event) {
dispatch('deviceDetectBySize')
});
}
},}
store / index.js
import Vue from 'vue'
import Vuex from 'vuex'
import main from "./main"
import dom from "./dom"
Vue.use(Vuex)
const Store = new Vuex.Store({
modules: {
main,dom,strict: process.env.NODE_ENV === 'development'
})
export default Store
如您所见,我从未在测试文件中使用过路由器对象,但存在与之相关的错误。
解决方法
因此,您的Login.vue
同时使用路由器(this.$router.push({name: 'dashboard'})
)和存储(...mapActions('main',['crud']),
),但您都不在安装架上使用。
也许您应该同时给它们:
import { mount,createLocalVue } from "@vue/test-utils"
import login from '@/pages/auth/login'
import Vuex from "vuex"
import VueRouter from "vue-router"
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(VueRouter)
const store = new Vuex.Store({
state: {
// Your minimal default state here
},actions: {
// at least the 'main.crud' action,here
}
})
const routes = [
// Here minimal routes
]
const router = new VueRouter({ routes })
describe("login",() => {
it("simple test",() => {
const wrapper = mount(login,{
router,store,localVue,data(){
return {
form: {
mobile_number: '',},}
},})
wrapper.find('#login').text()
})
})
此外,mapActions()
应该在methods
中,而不是data
(请参阅here)