前端自動化測試(一)
前端自動化測試(二)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
進行處理
往期回顧:
前端自動化測試(一)
前端自動化測試(二)