前几天突然了解到了jest单元测试,感觉挺有趣的,就趁机学一把,哈哈,我用其测试了我写的后台管理系统,大致了解了jest的使用,以及学习过程中学到的常用知识和在测试的过程中遇到的一些问题,我总结一下

1.jest是什么?

        Jest是 Facebook 的一套开源的 JavaScript 测试框架, 它自动集成了断言、JSDom、覆盖率报告等开发者所需要的所有测试工具,是一款几乎零配置的测试框架。它能支持很多框架,比如 BabelTypeScriptNodeReactAngularVue 等诸多框架。

         

2.vue中的jest的安装

        (1)执行安装命令        

        vue中使用jest,其实并没有我们想象的那么复杂,之前我使用jest的时候,查了较多资料,也配置了许多设置,繁忙且繁琐,直到最后我才发现,原来仅仅只需要执行一句指令即可

vue add @vue/cli-plugin-unit-jest

        这个命令会帮我们把相关的配置都配好,相关的依赖都装好,还会帮我们生成一个jest.config.js文件。

        (2)jest中常用的一些配置项的介绍

module.exports = {
    "moduleFileExtensions": [ //不需要配置
        "js",
        "json",
        // 告诉 Jest 处理 `*.vue` 文件
        "vue"
    ],
    testMatch: [ //test文件所在位置
        '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
    ],
    "transform": {  //不需要配置
        // 用 `vue-jest` 处理 `*.vue` 文件
        ".*\\.(vue)$": "<rootDir>/node_modules/vue-jest",
        // 用 `babel-jest` 处理 js
        "^.+\\.js$": "babel-jest"
    },
    "moduleNameMapper": { //不需要配置
        "^@/(.*)$": "<rootDir>/src/$1"
    },
    "collectCoverage": true,  //是否创建报告
    "collectCoverageFrom": ["**/*.{js,vue}", "!**/node_modules/**"], //创建报告的文件来源
    "coverageReporters": ["html", "text-summary"]   //报告的格式                
    "coveragePathIgnorePatterns":"[]"   //生成报告需要忽略的文件,默认值为 node_modules    
     "globals":{ //配置全局变量,此处我配置了一个全局变量VUE_APP_DATA,也可以在setup file中配置,如下说的lodash      
   "VUE_APP_DATA": {siteENV:'DEV'}} 
   setupFiles: ['<rootDir>/src/jest-setup.js'] //启动jest需要的文件


};

(3)在项目目录中创建tests文件,再创建unit文件,在其中文件命名的话,就以 xxx.spec.js命名(这个执行命令的时候,已经给我们创建了)

import { shallowMount } from '@vue/test-utils'
import explame from '@/components/Explame'

describe('Explame .vue', () => {
  it('renders props.msg when passed', () => {
    const msg = 'new message'
    const wrapper = shallowMount(Explame , {
      propsData: { msg }
    })
    expect(wrapper.text()).toMatch(msg)
  })
})

(4)在package.json 中添加启动命令,然后通过在控制台执行npm run test:unit ,进行测试

      Scripts配置:

 "test:unit": "vue-cli-service test:unit"

 3.jest的基本语法规则

      (1)Jest 支持三种方式写测试代码

  •   .spec.js
  •   .test.js
  •    放在 __tests__文件夹下         

    (2)测试结构

  • describe: 将几个相关的测试放到一个组中,非必须
  • test :(别名it)测试用例,是测试的最小单位
  • expect:提供很多的matcher 来判定你的方法返回值是否符合特定条件

describe('add的方法测试',()=>{

              test('2+3应该等于5',()=>{

                expect(add(2,3)).toBe(5)

        })

})

 (3)mock方法和 处理

  •  Jest的mock方式 (Jest.fn()、Jest.spyOn()、Jest.mock())
  • 预处理和后处理
    • beforeAll / afterAll  : 对测试文件中所有的用例开始前/ 后 进行统一的预处理
    • beforeEach/ afterEach :  在每个用例开始前 / 后 进行预处理

 (4)覆盖率指标

      在package.json中 设置 --coverage 即可 测试覆盖率

 "test:unit": "vue-cli-service test:unit --coverage"

 

  • %stmts是语句覆盖率(statement coverage):是不是每个语句都执行了?
  • %Branch分支覆盖率(branch coverage):是不是每个if代码块都执行了?
  • %Funcs函数覆盖率(function coverage):是不是每个函数都调用了?
  • %Lines行覆盖率(line coverage):是不是每一行都执行了?

配置好后,会生成一个coverage文件

 然后打开里面的index.html 里面会有详细的信息展示

 

 三种颜色分别代表不同比例的覆盖率(<50%红色,50%~80%灰色, ≥80%绿色)

旁边显示的1x代表执行的次数

 

好的测试覆盖率标准

      80%以下不及格,90%以上可以使用,95%以上优秀

4.常用的方法

        --mount: 创建一个包含被挂载和渲染的 Vue 组件的 wrapper,它仅仅挂载当前实例

        ---shallowMount:和 mount 一样,创建一个包含被挂载和渲染的 Vue 组件的 Wrapper,只挂载一个组件而不渲染其子组件 (即保留它们的存根),这个方法可以保证你关心的组件在渲染时没有同时将其子组件渲染,避免了子组件可能带来的副作用(比如Http请求等)

        ---shallowMount和mount的区别:在文档中描述为"不同的是被存根的子组件",大白话就是shallowMount不会加载子组件,不会被子组件的行为属性影响该组件

 为什么使用shallowMount而不使用mount?

        ---我认为单元测试的重点在"单元"二字,而不是"测试",想测试子组件再为子组件写对应的测试代码即可

        ---Wrapper:常见的有一下几种方法

  • Wrapper:Wrapper 是一个包括了一个挂载组件或 vnode,以及测试该组件或 vnode 的方法。
  • Wrapper.vm:这是该 Vue 实例。你可以通过 wrapper.vm 访问一个实例所有的方法和属性。
  • Wrapper.classes: 返回是否拥有该class的dom或者类名数组。
  • Wrapper.find:返回第一个满足条件的dom。
  • Wrapper.findAll:返回所有满足条件的dom。
  • Wrapper.html:返回html字符串。
  • Wrapper.text:返回内容字符串。
  • Wrapper.setData:设置该组件的初始data数据。
  • Wrapper.setProps:设置该组件的初始props数据。  (这是使用了,但没有效果)
  • Wrapper.trigger:用来触发事件。
<template>
	<div class="jest">
		<div class="name">{{name}}</div>
		<div class="name">{{name}}{{text}}</div>
		<div class="text" @click="add">{{text}}</div>
	</div>
</template>
<script src="./script.js">
export default {
	name:"Foo",
	props:{
		name:{
			type: String,
			default: '啦啦啦'
		}
	},
    data() {
        return {
            text: 123
        }
    },
    methods:{
    	add(){
			this.text += 1
		}
    }
}
</script>

     开始测试

import { shallowMount } from '@vue/test-utils'
import Foo from './Foo.vue'

describe('Foo', () => {
    const wrapper = shallowMount(Foo)
    console.log(Wrapper.classes())	//['jest']
    console.log(Wrapper.classes('jest'))	//true
    console.log(Wrapper.find('.name').text())	// 切记如果是类的话,要加点  : 啦啦
    console.log(Wrapper.findAll('.name'))	//返回dom数组  WrapperArray { selector: '.name' }
    console.log(Wrapper.findAll('.name').at(0))	//取dom数组中的第一个
    Wrapper.setData({text : 3})   //  设置一个值 
    console.log(Wrapper.vm.text)	// 3
    Wrapper.setProps({name : "拉拉"})
    console.log(Wrapper.vm.name)	//这个结果仍 为 啦啦啦
    Wrapper.find('.text').trigger("click")
    console.log(Wrapper.vm.text) // 4
})

     也可以初始化某些数据

import { shallowMount } from '@vue/test-utils'
import Foo from './Foo.vue'

const wrapper = shallowMount(Foo,{
    data() {
		return {
			bar: 'lala'
		}
      },
  
    propsData:{
    message: 'dd'
        },

     mocks: {
        $route: {
            query: {
                aaa: '1',
            }
        },
        $router: {
            push: jest.fn(),
            replace: jest.fn(),
        }
    }
    
})

5 Jest-Api(使用不同匹配器可以测试输入输出的值是否符合预期)toBe:判断是否相等

  • toBeNull:判断是否为null
  • toBeUndefined:判断是否为undefined
  • toBeDefined:与上相反
  • toBeNaN:判断是否为NaN
  • toBeTruthy:判断是否为true
  • toBeFalsy:判断是否为false
  • toContain:数组用,检测是否包含
  • toHaveLength:数组用,检测数组长度
  • toEqual:对象用,检测是否相等
  • toThrow:异常匹配
     
  • describe('Foo', () => {
    	expect(2 + 2).toBe(4)
    	expect(null).toBeNull()
    	expect(undefined).toBeUndefined()
    	let a = 1;
    	expect(a).toBeDefined()
    	a = 'ada';
    	expect(a).toBeNaN()
    	a = true;
    	expect(a).toBeTruthy()
    	a = false;
    	expect(a).toBeFalsy()
    	a  = [1,2,3];
    	expect(a).toContain(2)
    	expect(a).toHaveLength(3)
    	a = {a:1};
    	expect(a).toEqual({a:1})
    })
    

6.遇到的一些问题

        (1)解决国际化i18n 的报错

      Vue warn]: Error in config.errorHandler: "TypeError: _vm.$t is not a function"


import {shallowMount, mount, createLocalVue } from '@vue/test-utils';  // 引入 createLocalVue

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';

const localVue = createLocalVue()  // 创建临时的vue 实例

localVue.use(VueI18n)

const messages ={
    en:{
    ...enmsg,
    ...enLocale
    },
    zh:{
    ...zhmsg,
    ...zhLocale
    }
}

var i18n = new VueI18n({
    locale:'zh',
    message,
    })
window.Languages=i18n
   
  var Wrapper =shallowMount(XX(组件名),{
        localVue,
        // 用mock模拟
        mocks:{
            $t :(a,b)=>i18n.t(a,b)   // 解决报错
        }  
        
    })

       (2)引入了Element ui ,Element 组件会报错,提示没有注册,比如HelloWorld 组件中使用到了el-button组件,就会报错。 解决的话,还是创建一个vue的临时实例,将其挂载上去

建立一个文件jest.setup.js(方便之后其他测试文件使用)

import { config,createLocalVue } from '@vue/test-utils';
import ElementUI from 'element-ui';
const testVue = createLocalVue();
testVue.use(ElementUI);
export const localVue = testVue;

HelloWorld的测试文件使用

import { shallowMount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld'
import {localVue} from '@/jest-setup'
describe('HelloWorld.vue', () => {

  it('renders component', () => {
       // 将其传进去
    const wrapper = shallowMount(HelloWorld, {localVue})

    expect(wrapper.findAll('.hello-world').length).toBe(1)

  })

})

总结

这就是我所学到的jest单元测试方面的知识,之后在工作中如果遇到了jest方面新的问题,我会不断进行更新与完善,有句话说的好,“时人不识凌云木,直待凌云始道高”

一起加油吧,程序猿!

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐