前端自動化測試(一)javascript
前端自動化測試(二)css
經過前兩篇文章的學習,我相信你們對Jest
的核心用法能夠說是已經掌握了,這一篇文中咱們在Vue中使用Jest
前端
咱們能夠經過vue
官方提供的@vue/cli
直接建立Vue項目,在建立前須要先安裝好@vue/cli~
vue
這裏直接建立項目:java
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
文件node
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
目錄下!ios
咱們能夠查看pacakge.json
來執行對應的測試命令vue-cli
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit --watch" // 這裏增長個 --watch參數
},
複製代碼
開始測試 npm run test:unit
npm
咱們先忽略默認example.spec.js
文件,本身來嘗試下如何測試Vue組件
json
<template>
<div class="hello">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
複製代碼
HelloWorld
組件須要提供一個msg屬性,將msg屬性渲染到h1
標籤中,ok咱們來編寫測試用例
在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
組件,若是採用單元測試的方式,就須要依次測試每一個組件(單元測試是以最小單元來測試的) 可是單元測試沒法保證整個流程是能夠跑通的,因此咱們在單元測試的基礎上還要採用集成測試
總結:
1.單元測試能夠保證測試覆蓋率高,可是相對測試代碼量大,缺點是沒法保證功能正常運行
2.集成測試粒度大,廣泛覆蓋率低,可是能夠保證測試過的功能正常運行
3.通常業務邏輯會採用BDD方式採用集成測試(像測試某個組件的功能是否符合預期),通常工具方法會採用TDD的方式使用單元測試
4.對於 UI 組件來講,咱們不推薦一味追求行級覆蓋率,由於它會致使咱們過度關注組件的內部實現細節,從而致使瑣碎的測試
在測試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
進行處理
往期回顧: