實例展示vue單元測試及難題解惑

通過生動詳實的例子帶你排遍vue單元測試過程中的所有疑惑與難題。

技術棧:jest、vue-test-utils。

共四個部分:運行時、Mock、Stub、Configuring和CLI。

運行時

在跑測試用例時,大家的第一個絆腳石肯定是各種undifned報錯。

解決這些報錯的血淚史還歷歷在目,現在總結來看,大都是缺少運行時變量抑或異步造成的。

這里咱們只說運行時,基本就這兩類:

1.缺少window等環境變量

一般通過引入global-jsdom解決,這也是官方推薦的。當然我們也可以自己在測試代碼中直接聲明定義。

比如我們在業務代碼中使用了sessionStorage。

// procudtpay.vue
<script>
const sessionParams = window.sessionStorage.getItem('sessionParams')
export default {data () { }
}
</script>

然后在測試代碼中直接重定義,這樣在運行時,實際取到的值就是我們在這里定義的。

// procudtpay.spec.js
window.sessionStorage = {getItem: () => {return { name:'name', type:'type' }}
}
import procudtpay from '../views/procudtpay.vue'

這里關于執行順序做一點額外說明:

示例中sessionParams的賦值是在import引入.vue模塊就執行了的,所以對sessionStorage的定義賦值需要在引入之前。

如果你的sessionStorage取值是在vue實例化后,比如created中,那么則沒有該問題。

2.缺少在main.js中定義/注冊的全局屬性和方法

這些就需要在測試代碼中引入同款,以及通過mount的配置項mocks和stubs,分別對其進行mock或者存根了。

// main.js
import Vue from 'vue'
import Mint from 'mint-ui'
import '../filter'
import axios from 'axios'
Vue.use(Mint)
Vue.prototype.$post = (url, params) => {return axios.post(url, params).then(res => res.data)
}
Vue.filter('filterxxx', function (value) {// bala bala ba…
})// xxx.spec.js
import Vue from 'vue'
import '../../filter/filter'   // 引入注冊同款過濾器
Vue.filter('filterxxx', function (value) {// bala bala ba…
})
import { $post } from './http.js' 
it('快照測試', () => {const wrapper = shallowMount(ProductPay, {mocks: {$post  // 用自己定義的mock數據取代真實http請求},stubs:['mt-header'] // 存根組件})// ...
})

通常其他測試文件也會依賴這些全局變量,我們可以通過配置jest的setupFiles實現復用。

Mock

我翻開代碼一看,這代碼沒有注釋,歪歪斜斜的每一行都寫著‘斷言正確’四個字。我橫豎睡不著,仔細看了半夜,才從字縫里看出字來,滿屏都寫著兩個字:“造假”!

正應了那一句:人(ce)生(shi)如戲,全靠演技(mock)。總之,mock老重要了。

1.mock簡單函數

我們從最簡單的mock一個函數開始。

比如我們現在想要測試:當用戶購買成功,期望頁面能跳轉到結果頁。

// productpay.vue
<script>
export default {...methods:{commmit () {this.$post('xxx', params).then(data => {this.$router.push(`/payresult`)})}}
}
</script>

那么,我們可以通過mock掉$router的push方法,然后斷言它有被調用且參數正確,達成測試目的。

// productpay.spec.js
it('當用戶購買成功后,頁面應該跳轉至結果頁', async () => {const mockFunc = jest.fn()const wrapper = shallowMount(ProductPay, {mocks: {$post,$router: {push: mockFunc}}})wrapper.vm.commmit() // 提交購買expect(mockFunc).toHaveBeenCalledWith('/payresult')
})

2.mockHttp請求,指定返回結果

http請求和上面例子中的$router的區別是,它需要返回值。jest有多種方式指定返回值,這里用的是mockImplementation。

// test/**.spec.js
it('當用戶xxxx,應該xxxx', async () => {const respSuccess = { data: [...], code:0 }const respError = { data: [...], code:888 }// 定義mock函數const mockPost = jest.fn() const wrapper = shallowMount(index, { mocks: {$post:mockPost // 應用該mock函數}})// 指定異步返回數據mockPost.mockImplementation(() => Promise.resolve(respError))// 可以對調用情況進行斷言expect(mockPost).toHaveBeenCalled() mockPost.mockImplementation(() => Promise.resolve(respSuccess))//也可以等待異步結束,對結果進行斷言await flushPromises()expect(wrapper.vm.list).toEqual(respSuccess.data)
})

實際上我們項目中調用的接口會很多,且不乏返回大量數據的情況。如果這些都定義在測試代碼里就會很臃腫。這時候,我們可以對該功能做個簡單的模塊化。

// 常見的業務代碼
// main.js中把axios掛載到了vue實例
Vue.prototype.$post = (url, params) => {return axios.post(url, params).then(res => res.data)
}
// Index.vue中的請求
getProductList () {this.$post('/ProductListQry', {}).then(data => {this.ProductList = data.List})
}
// 1. 在單獨js中存放模擬數據 data/ProductListQry.js
export default {data:[{ id:1,name:'name',...},...],code:0
}// 2. 定義post方法,并做個數據匹配 test/http.js
import ProductListQry from '@/data/ProductListQry.js'
const mockData = {ProductListQry,... //可以用同樣的方式引入更多mock數據
}
const $post = (url = '') => {return new Promise((resolve, reject) => {const jsName = String(url).split('/')[1]resolve(mockData[jsName])})
}
export { $post }// 3. 引入并使用 test/index.spec.js
import Index from '@/views/Index.vue'
import { $post } from './http.js'
it('...',()=>{const wrapper = shallowMount(Index, {mocks: {$post}})wrapper.vm.getProductList() //觸發請求await flushPromises() //等待異步請求結束//可以看到wrapper中就有了我們指定的模擬數據console.log(wrapper.vm.ProductList) 
})

同理,如果要測試請求失敗的情形,可以再定義一個返回錯誤數據的方法,比如就叫$postError。

// test/**.spec.js
import { $postError } from './http.js'
it('...',()=>{const wrapper = shallowMount(Index, {mocks: {$post:$postError}})wrapper.vm.getProductList() //觸發請求await flushPromises() //等待異步請求結束// 我們就可以就獲取到錯誤數據的場景進行測試了console.log(wrapper.vm.ProductList) 
})

3.mock整個模塊

當業務代碼中直接使用了引入的組件/方法時,我們對其測試可能就需要mock整個模塊。下面是一個用彈窗做表單驗證的場景:

// productpay.vue
<script>
import { MessageBox } from '../Component'
export default {methods:{makeSurebuy () {let payAmount = delcommafy(this.payAmount)if (!payAmount) {MessageBox({message: '請先輸入購買金額'})return}if (payAmount < this.resData.BaseAmt) {MessageBox({message: '購買金額不能小于起存金額'})return}if (payAmount > this.Balance) {MessageBox({message: '購買金額不能大于可用余額'})return}// 校驗通過,發起交易...}}
}
<script>

//productpay.spce.js
import Component from '../Component'
jest.mock('../../../components/ZyComponent')it('當用戶點擊購買按鈕,如果輸入非法金額,應該有相應的錯誤提示', async () => {wrapper.findAll('.btn-commit').at(0).trigger('click')expect(Component.MessageBox.mock.calls[0][0]).toEqual({ message: '請先輸入購買金額' })wrapper.setData({payAmount: '100'})wrapper.findAll('.btn-commit').at(0).trigger('click')expect(Component.MessageBox.mock.calls[1][0]).toEqual({ message: '購買金額不能小于起存金額' })wrapper.setData({payAmount: '100000000000000000'})wrapper.findAll('.btn-commit').at(0).trigger('click')expect(Component.MessageBox.mock.calls[2][0]).toEqual({ message: '購買金額不能大于可用余額' })
})

我們通過jest.mock()mock整個模塊,當該模塊的方法被調用后它就會有一個mock屬性,可以通過ZyComponent.ZyMessageBox.mock進行訪問,其中ZyComponent.ZyMessageBox.mock.calls會返回被調用情況的數組,我們可以根據這個數據對函數被調用次數、入參情況進行斷言測試。

Stub存根組件

進行單元測試,理論上我們不用、也不應該在它的測試用例中測試子組件,不然就叫集成測試了。vue-test-utils是通過配置stubs實現對組件mock的。

const wrapper = shallowMount(index, {stubs: ['mt-header', 'mt-loadmore']
}

但是業務中難免會有調用子組件方法的時候,比如說mint-ui的loadmore。

// procuctlist.vue
<script>
export default {...methods:{getProductList () {this.$post('xxx', params).then(data => {...this.ProductList = this.ProductList.concat(data.List)this.$refs.loadmore.onBottomLoaded()})}}
}
</script>

這時候我們是可以改用mount方法使頁面渲染子組件,這樣通過$refs就能正常的獲取到子組件實例。但更合適的做法應該是自定義存根組件的內部實現,以滿足測試需求。

// procuctlist.spec.js
it('當用戶上拉產品列表,應該能看到的更多的產品', () => {const mockOnBottomLoaded = jest.fn()const mtLoadMore = {render: () => { },methods: {onBottomLoaded: mockOnBottomLoaded}}const mtHeader = {render: () => { }}const wrapper = shallowMount(Index, {stubs: { 'mt-loadmore': mtLoadMore, 'mt-header': mtHeader },mocks: {$post}})const currentPage = wrapper.vm.currentPagewrapper.vm.loadMoreProduction()expect(wrapper.vm.currentPage).toEqual(currentPage + 1)expect(mockOnBottomLoaded).toHaveBeenCalled()
})

最后提一嘴,存根組件后,業務代碼中子組件還是會被引入的,只是沒有被實例化和渲染。

Configuring和CLI

1.統計代碼覆蓋率忽略某些文件

使用coveragePathIgnorePatterns配置即可,把這個列出來是應為我遇到兩個項目相同配置,有一個死活不生效的問題。最后才從官方文檔中得知是babel插件istanbul問題。目前還未解決,只是粗暴的在.balelrc中把istanbul去掉了。有真正解決方案的大佬,留言教下……跪謝。

// jest.config.js
{coveragePathIgnorePatterns: ['<rootDir>/src/assets/']
}

2.通過t模式,可以僅執行指定的測試用例

當測試用例寫的多了,每次執行跑一堆用例,效率很低,如果代碼里有很多console,那就更難受了,找個報錯都能找半天。當時就想如果能僅測試當前用例就好了。

然后就找到了t模式,jest命令帶–watch參數進入監聽模式,然后輸入t,再輸入匹配規則即可。世界一下子就清凈了,舒服……

// package.json
{"scripts":{"tets":"jest --watch"}
}

3.vue-awesome-swiper測試運行時報錯

如果組件中引入了swiper,那么在執行測試用例時,vue-awesome-swiper中的js會報錯,引用即報錯,且是第三方代碼。

最后通過把swiper組件由局部注冊改為全局注冊得以解決。

行動吧,在路上總比一直觀望的要好,未來的你肯定會感 謝現在拼搏的自己!如果想學習提升找不到資料,沒人答疑解惑時,請及時加入扣群: 320231853,里面有各種軟件測試+開發資料和技術可以一起交流學習哦。

最后感謝每一個認真閱讀我文章的人,禮尚往來總是要有的,雖然不是什么很值錢的東西,如果你用得到的話可以直接拿走:

?

這些資料,對于【軟件測試】的朋友來說應該是最全面最完整的備戰倉庫,這個倉庫也陪伴上萬個測試工程師們走過最艱難的路程,希望也能幫助到你!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:
http://www.pswp.cn/web/13739.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/13739.shtml
英文地址,請注明出處:http://en.pswp.cn/web/13739.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【HarmonyOS4學習筆記】《HarmonyOS4+NEXT星河版入門到企業級實戰教程》課程學習筆記(九)

課程地址&#xff1a; 黑馬程序員HarmonyOS4NEXT星河版入門到企業級實戰教程&#xff0c;一套精通鴻蒙應用開發 &#xff08;本篇筆記對應課程第 16 節&#xff09; P16《15.ArkUI-狀態管理-任務統計案例》 1、實現任務進度卡片 怎么讓進度條和進度展示文本堆疊展示&#xff1…

./scripts/Makefile.clean 文件分析

文章目錄 目標 $(subdir-ymn)目標__clean $(clean-dirs): ??? make -f ./scripts/Makefile.clean obj$(patsubst _clean_%,%,$) $(clean-dirs)$(patsubst _clean_%,%,$)_clean_api _clean_cmd _clean_common _clean_disk _clean_drivers _clean_drivers/ddr/altera _clean_d…

react中的useEffect()的使用

useEffect()是react中的hook函數&#xff0c;作用是用于創建由渲染本身引起的操作&#xff0c;而不是事件的觸發&#xff0c;比如Ajax請求&#xff0c;DOM的更改 首先useEffect()是個函數&#xff0c;接受兩個參數&#xff0c;第一個參數是一個方法&#xff0c;第二個參數是數…

數據結構--樹與二叉樹--編程求以孩子兄弟表示法存儲的森林的葉結點個數

數據結構–樹與二叉樹–編程求以孩子兄弟表示法存儲的森林的葉結點個數 題目 編程求以孩子兄弟表示法存儲的森林的葉結點個數 ps&#xff1a;題目來源2025王道數據結構 思路 樹上的操作大多數是通過遞歸進行的 我們可以從根節點開始遞歸 如果結點 N 沒有孩子指針&#xff…

【Entity Framework】如何理解EF中的級聯刪除

【Entity Framework】如何理解EF中的級聯刪除 文章目錄 【Entity Framework】如何理解EF中的級聯刪除一、概述二、發生級聯行為時2.1/刪除主體/父實體2.2/斷開關系 三、發生級聯行為的位置3.1/級聯刪除被跟蹤實體3.2/數據庫中的級聯刪除 四、級聯NULL 一、概述 Entity Framewo…

vue3 路由跳轉 攜帶參數

實現功能&#xff1a;頁面A 跳轉到 頁面B&#xff0c;攜帶參數 路由router.ts import { createRouter, createWebHistory } from "vue-router";const routes: RouteRecordRaw[] [{path: "/demo/a",name: "aa",component: () > import(&quo…

x264 碼率控制原理:x264_ratecontrol_start 函數

x264_ratecontrol_start 函數 函數原理 函數功能:編碼一幀之前,為當前幀選擇一個量化 QP,屬于幀級別碼率控制;這對于控制視頻質量和文件大小至關重要。通過調整QP,編碼器可以在保持視頻質量的同時,盡可能減小輸出文件的大小。函數參數:x264_t *h: 編碼器上下文結構體指…

十七、個人信息出境標準合同的具體內容有哪些?

根據《標準合同辦法》第六條&#xff0c;標準合同應當嚴格按照網信辦制定版本訂立&#xff0c;個人信息處理者可以與境外接收方約定其他條款&#xff0c;但不得與標準合同相沖突。 根據《標準合同辦法》附件&#xff0c;目前版本的標準合同內容主要包括&#xff1a; 1. 個人信…

Flutter 中的 TextButton 小部件:全面指南

Flutter 中的 TextButton 小部件&#xff1a;全面指南 在Flutter的世界里&#xff0c;TextButton是一個基礎的小部件&#xff0c;用于創建只包含文本的按鈕。它通常用于對話框、表單以及需要強調主要操作的界面。本文將為您提供一個全面的指南&#xff0c;幫助您了解如何使用T…

地信遙感測繪電子書

《地理信息系統概論》&#xff0c;黃杏元&#xff0c;馬勁松編著&#xff0c;第三版&#xff0c;高等教育出版社&#xff0c;2008年 《地理信息系統》&#xff08;第二版&#xff09;湯國安&#xff0c;趙牡丹&#xff0c;楊昕等編&#xff0c;高等教育出版社&#xff0c;2010…

【stm32/CubeMX、HAL庫】嵌入式實驗五:定時器(2)|PWM輸出

參考&#xff1a; 【【正點原子】手把手教你學STM32CubeIDE開發】 https://www.bilibili.com/video/BV1Wp42127Cx/?p13&share_sourcecopy_web&vd_source9332b8fc5ea8d349a54c3989f6189fd3 《嵌入式系統基礎與實踐》劉黎明等編著&#xff0c;第九章定時器&#xff0c…

8操作系統定義、分類及功能+設備管理+作業管理 軟設刷題 軟考+

操作系統定義、分類及功能設備管理作業管理 知識點1-55-1010-1515-2020-2525-3030-35 刷題操作系統定義、分類及功能1-55-1010-15作業管理1-5設備管理1-55-10 知識點 1-5 1 嵌入式操作系統的特點&#xff1a; 1.微型化&#xff0c;從性能和成本角度考慮&#xff0c;希望占用的…

145.棧和隊列:刪除字符串中的所有相鄰重復項(力扣)

題目描述 代碼解決 class Solution { public:string removeDuplicates(string s) {// 定義一個棧來存儲字符stack<char> st;// 遍歷字符串中的每一個字符for(int i 0; i < s.size(); i){// 如果棧為空或棧頂字符與當前字符不相同&#xff0c;則將當前字符入棧if(st.e…

Jenkins的Pipeline流水線

目錄 前言 流水線概念 什么是流水線 Jenkins流水線 pipeline node stage step 創建一個簡單的流水線 創建Pipeline項目 選擇模板 測試 前言 提到 CI 工具&#xff0c;首先想到的就是“CI 界”的大佬——Jenkjns,雖然在云原生爆發的年代,蹦出來了很多云原生的 CI 工具…

Hive的窗口函數

定義&#xff1a; 聚合函數是針對定義的行集(組)執行聚集,每組只返回一個值.如sum()、avg()、max() 窗口函數也是針對定義的行集(組)執行聚集,可為每組返回多個值.如既要顯示聚集前的數據,又要顯示聚集后的數據.步驟&#xff1a; 1.將記錄分割成多個分區. 2.在各個分區上調用窗…

word-表格疑難雜癥診治

一、用表格進行排版圖片、制作公文頭 可以在插入圖片時固定列寬 二、表格中的疑難雜癥 問題一&#xff1a;表格超過頁面&#xff0c;右側文字看不見 解決&#xff1a;表格窗口-布局-自動調整-根據窗口自動調整表格 問題二&#xff1a;表格底部文字被遮擋 解決&#xff1a;布…

ArcGIS Maps SDK for JS:使用queryFeatures方法查詢 FeatureLayer 中符合條件的要素

文章目錄 方式一&#xff1a;使用featureLayer.createQuery()方法方式二&#xff1a;使用 Query 構造函數方式三&#xff1a;簡化寫法 要想查詢FeatureLayer 圖層中滿足某些條件的要素&#xff0c;可以使用ArcGIS API for JavaScript 提供的queryFeatures() 方法和 Query 對象進…

【linux】yumvim工具理解使用

目錄 Linux 軟件包管理器 yum 關于 rzsz 注意事項 查看軟件包 Linux開發工具 Linux編輯器-vim使用 vim的基本概念 vim的基本操作 vim正常模式命令集 vim末行模式命令集 簡單vim配置 配置文件的位置 sudo提權 Linux 軟件包管理器 yum 1.yum是什么&#xff1…

攻防世界---web---warmup

1、題目描述 2、查看源碼&#xff0c;發現有個source.php 3、訪問該文件&#xff0c;得到這一串代碼 4、分析代碼 5、訪問hint.php&#xff0c;提示flag在ffffllllaaaagggg這個文件下 6、構造payload ?filesource.php?/../../../../../../ffffllllaaaagggg

Fitting Parameterized Three-Dimensional Models to Images

摘要 基于模型的識別和運動跟蹤依賴于解決投影和模型參數&#xff0c;使其最佳適應匹配的2D圖像特征的3D模型的能力。本文將當前的參數求解方法擴展到處理具有任意曲面和任意數量的內部參數&#xff08;表示關節、可變尺寸或表面變形&#xff09;的對象。開發了數值穩定化方法…