前端自动化测试(一)
前端自动化测试(二)javascript
经过前两篇文章的学习,我相信你们对Jest
的核心用法能够说是已经掌握了,这一篇文中咱们在Vue中使用Jest
css
咱们能够经过vue
官方提供的@vue/cli
直接建立Vue项目,在建立前须要先安装好@vue/cli~
前端
这里直接建立项目:vue
vue create vue-unit-project
? Please pick a preset: default (babel, eslint) ❯ Manually select features # 手动选择
? Check the features needed for your project: ◉ Babel ◯ TypeScript ◯ Progressive Web App (PWA) Support ◉ Router ◉ Vuex ◯ CSS Pre-processors ◯ Linter / Formatter ❯◉ Unit Testing ◯ E2E Testing
? Please pick a preset: Manually select features ? Check the features needed for your project: Babel, Router, Vuex, Unit ? Use history mode for router? # history模式 ion) Yes ? Pick a unit testing solution: Jest # 测试框架选择Jest ? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config # 将配置文件产生独立的文件 files ? Save this as a preset for future projects? (y/N) # 是否保存配置
初始化成功后,咱们先来查看项目文件,由于咱们主要关注的是测试,因此先查看jest.config.js
文件java
module.exports = { moduleFileExtensions: [ // 测试的文件类型 'js','jsx','json','vue' ], transform: { // 转化方式 '^.+\\.vue$': 'vue-jest', // 若是是vue文件使用vue-jest解析 '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', // 若是是图片样式则使用 jest-transform-stub '^.+\\.jsx?$': 'babel-jest' // 若是是jsx文件使用 babel-jest }, transformIgnorePatterns: [ // 转化时忽略 node_modules '/node_modules/' ], moduleNameMapper: { // @符号 表示当前项目下的src '^@/(.*)$': '<rootDir>/src/$1' }, snapshotSerializers: [ // 快照的配置 'jest-serializer-vue' ], testMatch: [ // 默认测试 /test/unit中包含.spec的文件 和__tests__目录下的文件 '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' ], testURL: 'http://localhost/', // 测试地址 watchPlugins: [ // watch提示插件 'jest-watch-typeahead/filename', 'jest-watch-typeahead/testname' ] }
经过配置文件的查看咱们知道了全部测试都应该放在tests/unit
目录下!node
咱们能够查看pacakge.json
来执行对应的测试命令ios
"scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "test:unit": "vue-cli-service test:unit --watch" // 这里增长个 --watch参数 },
开始测试 npm run test:unit
vue-cli
咱们先忽略默认example.spec.js
文件,本身来尝试下如何测试Vue组件
npm
<template> <div class="hello"> <h1>{{ msg }}</h1> </div> </template> <script> export default { name: 'HelloWorld', props: { msg: String } } </script>
HelloWorld
组件须要提供一个msg属性,将msg属性渲染到h1
标签中,ok咱们来编写测试用例json
在tests/unit
下建立 HelloWorld.spec.js
import Vue from 'vue'; import HelloWorld from '@/components/HelloWorld' describe('测试HelloWolrd 组件',()=>{ it('传入 msg 属性看可否渲染到h1标签内',()=>{ const baseExtend = Vue.extend(HelloWorld); // 获取当前组件的构造函数,而且挂载此组件 const vm = new baseExtend({ propsData:{ msg:'hello' } }).$mount(); expect(vm.$el.innerHTML).toContain('hello'); }) });
这样一个简单的Vue组件就测试成功了!可是写起来感受不简洁也不方便,因此为了更方便的测试,Vue官方提供给咱们了个测试工具Vue Test Utils
,并且这个工具为了方便应用,采用了同步的更新策略。
import Vue from 'vue'; import HelloWorld from '@/components/HelloWorld'; import {shallowMount} from '@vue/test-utils' describe('测试HelloWolrd 组件',()=>{ it('传入 msg 属性看可否渲染到h1标签内',()=>{ const wrapper = shallowMount(HelloWorld,{ propsData:{ msg:'hello' } }) expect(wrapper.find('h1').text()).toContain('hello') }); });
这样写测试是否是很high,能够直接渲染组件传入属性,默认返回wrapper
,wrapper
上提供了一系列方法,能够快速的获取DOM
元素! 其实这个测试库的核心也是在 wrapper
的方法上,更多方法请看 Vue Test Utils
这里的shallowMount
被译为潜渲染,也就是说HelloWorld
中引入其余组件是会被忽略掉的,固然也有深度渲染mount
方法!
刚才写测试的这种方式就是先编写功能!编写完成后,咱们来模拟用户的行为进行测试,并且只测试其中的某个具体的功能!这就是咱们所谓的 BDD形式的单元测试。接下来,咱们再来换种思路写个组件!
这回呢,咱们采用TDD的方式来测试,也就是先编写测试用例
先指定测试的功能: 咱们要编写Todo组件
编写Todo.spec.js
import Todo from '@/components/Todo.vue'; import {shallowMount} from '@vue/test-utils' describe('测试Todo组件',()=>{ it('当输入框输入内容时会将数据映射到组件实例上',()=>{ // 1) 渲染Todo组件 let wrapper = shallowMount(Todo); let input = wrapper.find('input'); // 2.设置value属性 并触发input事件 input.setValue('hello world'); // 3.看下数据是否被正确替换 expect(wrapper.vm.value).toBe('hello world') }); it('若是输入框为空则不能添加,不为空则新增一条',()=>{ let wrapper = shallowMount(Todo); let button = wrapper.find('button'); // 点击按钮新增一条 wrapper.setData({value:''});// 设置数据为空 button.trigger('click'); expect(wrapper.findAll('li').length).toBe(0); wrapper.setData({value:'hello'});// 写入内容 button.trigger('click'); expect(wrapper.findAll('li').length).toBe(1); }); it('增长的数据内容为刚才输入的内容',()=>{ let wrapper = shallowMount(Todo); let input = wrapper.find('input'); let button = wrapper.find('button'); input.setValue('hello world'); button.trigger('click'); expect(wrapper.find('li').text()).toMatch(/hello world/); }); });
咱们为了跑通这些测试用例,只能被迫写出对应的代码!
<template> <div> <input type="text" v-model="value" /> <button @click="addTodo"></button> <ul> <li v-for="(todo,index) in todos" :key="index">{{todo}}</li> </ul> </div> </template> <script> export default { methods: { addTodo() { this.value && this.todos.push(this.value) } }, data() { return { value: "", todos: [] }; } }; </script>
以上就是咱们针对Todo这个组件进行的单元测试,可是真实的场景中可能会更加复杂。在真实的开发中,咱们可能将这个Todo
组件进行拆分,拆分红TodoInput
组件和TodoList
组件和TodoItem
组件,若是采用单元测试的方式,就须要依次测试每一个组件(单元测试是以最小单元来测试的) 可是单元测试没法保证整个流程是能够跑通的,因此咱们在单元测试的基础上还要采用集成测试
总结:
在测试Vue项目中,咱们可能会在组件中发送请求,这时咱们仍然须要对请求进行mock
<template> <ul> <li v-for="(list,index) in lists" :key="index">{{list}}</li> </ul> </template> <script> import axios from 'axios' export default { async mounted(){ let {data} = await axios.get('/list'); this.lists = data; }, data() { return { lists: [] }; } }; </script>
能够参考上一篇文章:如何实现jest
进行方法的mock
import List from "@/components/List.vue"; import { shallowMount } from "@vue/test-utils"; jest.mock("axios"); it("测试List组件", done => { let wrapper = shallowMount(List); setTimeout(() => { expect(wrapper.findAll("li").length).toBe(3); done(); }); });
这里使用setTimeout的缘由是咱们本身mock的方法是promise,因此是微任务。咱们指望微任务执行后再进行断言,因此采用setTimeout进行包裹,保证微任务已经执行完毕。若是组件中使用的不是async、await
形式,也能够使用$nextTick
, (新版node中await
后的代码会延迟到下一轮微任务执行)。
举个栗子:
function fn(){ return new Promise((resolve,reject)=>{ resolve([1,2,3]); }) } async function getData(){ await fn(); // await fn() 会编译成 // new Promise((resolve)=>resolve(fn())).then(()=>{ // console.log(1) // }) console.log(1); } getData(); Promise.resolve().then(data=>{ console.log(2); });
固然不一样版本执行效果可能会有差别
来简单看下不是async、await
的写法~~~
axios.get('/list').then(res=>{ this.lists = res.data; })
it('测试List组件',()=>{ let wrapper = shallowMount(List); // nextTick方法会返回一个promise,由于微任务是先进先出,因此nextTick以后的内容,会在数据获取以后执行 return wrapper.vm.$nextTick().then(()=>{ expect(wrapper.vm.lists).toEqual([1,2,3]) }) })
咱们写了一个切换显示隐藏的组件,当子组件触发change事件时能够切换p标签的显示和隐藏效果
<template> <div> <Head @change="change"></Head> <p v-if="visible">这是现实的内容</p> </div> </template> <script> import Head from './Head' export default { methods:{ change(){ this.visible = !this.visible; } }, data(){ return {visible:false} }, components:{ Head } } </script>
咱们来测试它!能够直接经过wrapper.find
方法找到对应的组件发射事件。
import Modal from '@/components/Modal'; import Head from '@/components/Head'; import {mount, shallowMount} from '@vue/test-utils' it('测试 触发change事件后 p标签是否能够切换显示',()=>{ let wrapper = shallowMount(Modal); let childWrapper = wrapper.find(Head); expect(wrapper.find('p').exists()).toBeFalsy() childWrapper.vm.$emit('change'); expect(childWrapper.emitted().change).toBeTruthy(); // 检验方法是否被触发 expect(wrapper.find('p').exists()).toBeTruthy(); // 检验p标签是否显示 })
到这里咱们对vue
的组件测试已经基本搞定了,接下来咱们再来看下如何对Vue中的Vuex
、Vue-router
进行处理
往期回顾:
前端自动化测试(一)
前端自动化测试(二)