前言
前一段时间公司让前端写单元测试,于是乎就研究了下JEST。从安装到落地一路坎坷呀,因为淋雨过所以想为人撑伞,把自己遇到的问题和搜集到的都分享出来,欢迎大家补充内容。(技术是没边界的,即使我在公司内部也分享过,仍不影响继续分享给大家)
一、安装包并运行命令后报错类问题
1、未安装测试运行器运行环境jest-environment-jsdom
window is undefined, vue-test-utils needs to be run in a browser environment.You can run the tests in node using jsdom
早期的jest会默认安装JSDOM的运行环境,但是在28版本后,已取消默认安装,需要自己手动安装jest-environment-jsdom,执行jest运行环境。
npminstall --save-dev jest-environment-jsdom
在package.json中加入
// package.json{// ..."jest":{// ..."testEnvironment":"jsdom"}}
2、安装jest-babel报错
2.1 提示:Error: Cannot find module ‘babel-core’
查找issue后发现,主要是因为babel-loader和babel-core版本不匹配导致的。
babel-loader v8.x 应该对应 babel-core v7.x
babel-loader v7.x 应该对应 babel-core v6.x
所以需降低babel-core版本:
npminstall --save-dev babel-core@^7.0.0-bridge.0
2.2 提示:Requires Babel “^7.0.0-0”, but was loaded with “6.26.3”.
描述:
安装
jest-babel
后,提示让安装
babel-core
,执行
npm install --save-dev babel-core
安装后版本不匹配报错。
解决:
安装指定版本 “babel-core”: “^7.0.0-bridge.0”
npm uninstall jest babel-jest babel-core @babel/core
npminstall --save-dev jest babel-jest babel-core@^7.0.0-bridge.0 @babel/core
3、提示:ReferenceError: Vue is not defined
查找 issue 后发现,其原因主要是 jest-environment-jsdom 和@vue/test-utils配置冲突导致的。引入 jest-environment-jsdom时,jest会自动设置特定的导入方式,再引入@vue/test-utils时设置了customExportConditions配置项,导致Jest 最后使用了CommonJS的导入方式,修改如下:
// package.json
{
// ...
"jest": {
// ...
"testEnvironmentOptions": {
"customExportConditions": ["node", "node-addons"]
},
}
}
4、提示:TypeError: Cannot destructure property ‘createComponentInstance’ of ‘Vue.ssrUtils’ as it is undefined
此问题是由于@vue/test-utils与vue版本不匹配导致的,按照官网的描述:
@vue/test-utils v2.x 应该对应 vue v3.x
@vue/test-utils v1.x 应该对应 vue v2.x
需对@vue/test-utils进行降级处理:
npminstall--save @vue/[email protected]
二、测试用例编写过程遇到的坑点
1、在测试组件中使用了第三方组件或其他基础组件,如Element UI等
报错场景包括:
- 测试组件使用了全局定义的第三方组件
Vue.use(ElementUI, { locale, size: 'small' })
- 测试组件按需引入第三方组件
import { Input } from 'element-ui'
- 自定义基础组件
import CustomerInput from '@/components/CustomerInput.vue'
解决方式如下:
- 在挂载选项中注册对应的组件
const wrapper =mount(CustomerInputNumber,{components:{'el-input': Input,'CustomerInput': CustomerInput
}});
其中,注册组件可以是自定义的模拟组件,也可以在测试用例中按需引入的第三方组件
import CustomerInput from'@/components/CustomerInput.vue'const mockComponent ={template:"<div><slot></slot></div>",props:{color: String
}};const wrapper =mount(CustomerInputNumber,{components:{'el-input': mockComponent,'CustomerInput': CustomerInput
}});
- 使用临时Vue实例全局注册组件
import{ createLocalVue }from'@vue/test-utils'import{ Input }from'element-ui'const localVue =createLocalVue()
localVue.use(Input)const wrapper =mount(CustomerInputNumber,{
localVue
});
2、在测试组件中直接使用node_modules中的文件
例如,在测试组件中,直接使用了node_modules中的Emitter组件。但是在Jest转译时,会默认将node_modules忽略,所以当测试用例执行到该行时,会因为获取不到导出的Emitter组件而报错。
在报错信息中,Jest给出的处理建议,是将Emitter组件所在目录加入到转译文件中,需要对Jest做如下配置:
// package.json{// ..."jest":{// ..."transformIgnorePatterns":["<rootDir>/node_modules/(?!(element-ui/src/mixins/emitter))"],}}
3、在测试组件的Constructer中使用全局依赖
如果提示在测试组件中找不到挂载在全局实例上的方法,如 lodash.throttle() 而在实际的测试用例运行时,已经传入了loadsh的mocks注册,并且在测试组件的mounted生命周期中,Vue实例上可获取到mocks注册的loadsh属性。
// 测试用例import lodash from'lodash'const wrapper =mount(TestComponent,{mocks:{"_": lodash
},});
// 测试组件exportdefaultclassTestComponentextendsVue{// private onScrollListener = this._.throttle(this.onScroll, 300) // 先注释报错行privatemounted(){
console.log(this._)// 可以正常输出}}
原因是,运行测试用例挂载(mount、shallowMount)测试组件时,如果测试组件使用了全局注册的属性或方法,只能在mounted生命周期后才能获取到由测试用例mocks注册的模拟属性或方法。而Constructor生命周期在mounted之前执行,此时去获取就会提示undefined。
解决方法是:在测试组件中按需引入lodash属性,不使用全局注册,这样就能在Constructor生命周期中获取到依赖:
// 测试组件import _ from'lodash'exportdefaultclassTestComponentextendsVue{private onScrollListener = _.throttle(this.onScroll,300)}
4、提示:SyntaxError: Unexpected token ‘:’ jest单测样式问题
描述:jest单测样式问题
处理方式:
1) 安装并配置 identity-obj-proxy,可以进行样式的代理处理
- (1)单独jest配置文件:
module.export ={'moduleNameMapper':{'\\.(css|less)$':'identity-obj-proxy'}}
- (2)package.json进行配置:
"jest":{"moduleNameMapper":{"\\.(css|less)$":"identity-obj-proxy"}}
2) 创建mock文件
注:rootDir相当于package.json文件的路径
- 单独jest配置文件:
module.export ={'moduleNameMapper':{'\\.(css|less)$':'<rootDir>/__mocks__/styleMock.js'}}
- 文件目录./mocks/styleMock.js内:
module.exports = {};
5、提示:Cannot read property ‘_t‘ of undefined Unknown custom 单元测试 jest element-ui国际化i18n报错解决
报错点:
- 1.Cannot read property ‘_t’ of undefined
- 2.Unknown custom element: - did you register the component correctly?
// 1导入:vue测试工具类// import Vue from 'vue';import{shallowMount, mount, createLocalVue }from'@vue/test-utils';const localVue =createLocalVue();// 2导入:element-uiimport ElementUI from'element-ui';import'element-ui/lib/theme-chalk/index.css';// 3导入:vueximport store from'**/store/index.js';// 4导入:mixinimport mixin from'**/views/js/mixin.js';
localVue.mixin(mixin);// 5导入:要测试的组件import myComponents from'**/views/myComponents.vue';/* --------中引文switch start----------------- */import VueI18n from'vue-i18n';import enLocale from'element-ui/lib/locale/lang/en';import zhLocale from'element-ui/lib/locale/lang/zh-CN';import zhmsg from'../../public/lang/zh.json';import enmsg from'../../public/lang/en.json';
localVue.use(VueI18n)const messages ={en:{...enmsg,...enLocale
},zh:{...zhmsg,...zhLocale
}};// Create VueI18n instance with options 中引文switchvar i18n =newVueI18n({locale:'zh',
messages,// set locale messagessilentTranslationWarn:true});
window.Languages = i18n;// 这里全部注册element:解决报错2
localVue.use(ElementUI,{i18n:function(path, options){let value = i18n.t(path, options);if(value !==null&& value !==undefined)return value;return'';}});// 6.更多匹配器请看官网 (jest官网 https://www.jestjs.cn/)var wrapper =mount(myComponents,{
localVue,
store,mocks:{$t:(a,b)=> i18n.t(a,b),// 这里处理国际化:解决报错1},});// 测试套件describe('myComponents-vue组件',()=>{// 测试用例// 1.测试data上面的值test('测试data上面的值:',()=>{expect(wrapper.vm.msg).toBe('myComponents value');});test('点击按钮:',()=>{
console.log('wrapper.vm.userInfo:',wrapper.vm.userInfo);let btnT = wrapper.find('#btn').text()
console.log('btnT-text:', btnT);expect(btnT).toBe('确定');});// 2.调用组件的方法methodtest('调用组件的方法method:--登录方法',async()=>{// 准备数据
wrapper.vm.form ={username:'admin',password:'123456'}
wrapper.vm.sureLogin();
console.log('this.userInfo.username', wrapper.vm.userInfo.username);expect(wrapper.vm.userInfo).toBeTruthy()});test('关闭按钮:',()=>{let btnT = wrapper.find('#cancelBtn').text()
console.log('btnT:', btnT);expect(btnT).toBe('关闭');});});
6、提示:TypeError: Cannot read property ‘query’ of undefined vue jest路由器路由错误 this.$route.query
import{ createLocalVue, shallowMount }from'@vue/test-utils'import VueRouter from'vue-router'import myComponents from'**/views/myComponents.vue';const localVue =createLocalVue();
localVue.use(VueRouter)const router =newVueRouter();describe('myComponents.vue',()=>{let wrapper;beforeEach(()=>{
wrapper =shallowMount(myComponents,{
localVue,
router
});});it('renders correctly',()=>{expect(wrapper.element).toMatchSnapshot();});})
7、提示:TypeError: symbol is not a function 被测vue组件中使用require 引入图片报错
在Vue.js项目中使用 require 引入图片时,如果你想在单元测试中使用Jest跳过这些引入,可以使用Jest的jest.mock功能来模拟这些require调用。
以下是一个简单的例子,展示了如何模拟图片引入:
假设你有一个组件MyComponent.vue,其中使用require来引入一个图片:
<template><div><img:src="imageSrc"alt="example image"></div></template><script>exportdefault{data(){return{imageSrc:require('@/assets/example.png')};}};</script>
在你的单元测试文件中,你可以使用jest.mock来模拟require:
import{ shallowMount, createLocalVue }from'@vue/test-utils';import MyComponent from'@/components/MyComponent.vue';
jest.mock('@/assets/example.png',()=>'mocked-image-url');describe('MyComponent.vue',()=>{let wrapper;beforeEach(()=>{const localVue =createLocalVue();
wrapper =shallowMount(MyComponent,{ localVue });});it('renders correctly',()=>{expect(wrapper.element).toMatchSnapshot();});});
在这个例子中,jest.mock函数模拟了@/assets/example.png的返回值为’mocked-image-url’,这样在测试执行时,require调用就会被这个模拟值所替代,从而不会试图加载实际的图片文件,避免了测试执行时的错误。
版权归原作者 佚名猫 所有, 如有侵权,请联系我们删除。