Jest | 讓 Jest 為你的 Code 做測試-基礎用法教學
前言
單元測試是進入前端工程後一直很想學的技能,主要是做過測試能讓自己的程式碼維持一定的水準,尤其在團隊開發時,更不會在上線時因為沒注意到某個細節而產生 Bug ,導致專案出現問題。
單元測試
單元測試是指為專案中每個單一行為做測試,通常專案裡的最小單位都是一個 function ,當每個 function 經過測試,確保邏輯是正確的,那當他在專案裡運行時也就比較不會發生問題,且留下的測試文件也可以在團隊討論或交接時更清楚。
Jest
用於前端的測試框架不是只有 Jest ,選擇原因是因為筆者較擅長使用 React ,而 Jest 在測試方面和 React 的整合度較佳。除了 Jest 以外,常聽到的測試框架還有 mocha 。
建立專案
首先到專案資料夾的目錄下,在 node.js 環境下創建 npm 專案:
npm init -y
這時候在專案資料夾內會產生記錄著專案開發設定的 package.json ,如果還不曉得如何安裝 node.js 可以參考「第一次建置node.js開發環境和安裝npm就上手!」的說明。
下載套件
透過 npm 套件管理工具下載 Jest 測試框架到專案的執行環境中:
npm install jest --save-dev
設定測試指令
打開 package.json ,可以看到剛剛下載的 Jest 已經被記錄在 devDependencies 中了:

在上圖第一行的 scripts 是在 npm 的開發環境中設定執行指令的地方,上方的 test 便是預設的指令之一,可以直接把他的內容改掉,如下:
scripts: {
"test": "jest"
}
設定好後,只需在終端機中使用以下方式輸入,便能執行設定好的對應指令:
npm run test //對應指令
需要注意的是,如果是使用 npm 的 scripts 執行指令,那他的執行環境就是專案本身,尋找執行環境中的 Jest 或其他套件執行。
換個說法,直接在終端機中輸入 jest ,執行環境就不會在專案內,而是以全域為主,但是全域環境下並沒有安裝 Jest 便會出錯,除非將 Jest 安裝在全域中:
npm install jest -g // -g 代表全域
建立測試
Jest 在執行測試時,會尋找專案中副檔名為 .test.js 結尾的檔案,但不限制要放在哪個資料夾,所以在根目錄新增一個 index.test.js 建立第一個測試:
test('Check the result of 5 + ', () => {
expect(5 + 2).toBe(7)
})把上方的 test 當作一個函式,負責描寫一個單元測試,他擁有兩個參數:
- 第一個參數為「測試名稱」,能夠簡單描述這部分是在測試什麼邏輯或功能。
- 第二個參數是一個函式,又稱斷言,函式內的
expect用來描述被測試的內容,toBe是測試內容的回傳值是否符合期望值,例如上方的測試內容為「5加上2期望會等於7」。
執行測試
在終端機中輸入在 scripts 中設定好的指令:
npm run test
執行後會顯示測試的結果:

結果內會顯示 Jest 測試了哪些 .test.js 檔案,還有每個測試( expect )內的結果( toBe )是否正確符合,符合的話會輸出 PASS。
現在把 toBe 內的數字改成 8 ,再進行一次測試:

當測試失敗時, Jest 會再結果中顯示哪個檔案內的測試有問題,並會提示正確的結果 Received 。
這就是使用 Jest 測試的基本方法,但實際上需要測試的函式都已經寫好了,並不會在 expect 中寫下邏輯,因此可以直接在 expect 中呼叫函式,測試結果的正確性,例如:
//要測試的函式
const sum = (a, b) => {
return a + b
}
test('Check the result of 5 + 2', () => {
//在 expect 中呼叫函式測試結果
expect(sum(5, 2)).toBe(7)
})其他斷言
斷言的種類有很多,上方的 toBe 只是其中一種測試方式,除此之外還有以下的斷言可以使用:
除了 toBe 外,對字串還可以用 toMatch 搭配正規表達式檢查:
test('Use toMatch test',()=>{
//搭配正規表達式
expect('Happy new year.').toMatch(/new/)
})確認物件是否等於期望值需使用 toEqual :
test('Check the object type', () => {
let peopleA = {
name: 'GQSM'
}
peopleA.age = 25
//測試字串
expect(peopleA.name).toBe('GQSM')
//測試物件
expect(peopleA).toEqual({ name: 'GQSM', age: 25 })
})預防函式回傳某個結果可以使用 not :
test('Use not', () => {
let peopleA = {
name: 'GQSM'
}
//確認 name 不等於空
expect(peopleA.name).not.toBe('')
peopleA.name = ''
//如果 name 是空的
expect(peopleA.name).not.toBe('')
})上方的第 9 行 peopleA.name 是空的,而在使用 not 的情況下結果需不等於期望值,所以不會通過測試:

確認數字結果的斷言:
test('test integer', () => {
//整數可以使用 toBe 或 toEqual 斷言
expect(5).toBe(5)
expect(5).toEqual(5)
//測試輸出值是否大於期望值
expect(5).toBeGreaterThan(4)
//測試輸出值是否大於等於期望值
expect(5).toBeGreaterThanOrEqual(5)
//測試輸出值是否小於期望值
expect(5).toBeLessThan(6)
//測試輸出值是否小於期望值
expect(5).toBeLessThanOrEqual(5)
})需要注意在 JavaScript 中的小數點運算會產生誤差,因此浮點數需要使用 toBeCloseTo 做斷言,他會捨棄掉些微的誤差:
test('Test float', () => {
//會忽略些微的誤差
expect(0.1 + 0.2).toBeCloseTo(0.3)
//需完全相等
expect(0.1 + 0.2).toBe(0.3)
})上方使用 toBe 會不通過測試,因為 toBe 需要完全符合:

對陣列可以用 toContain 判斷陣列內是否含有某值,或搭配迴圈對每個位置做斷言:
test('For array test in jest',()=>{
let arrA = ['A','B','C']
//檢查陣列內是否含有某值
expect(arrA).toContain('B')
//搭配迴圈檢查每個位置都不等於空
for(let i in arrA){
expect(arrA[i]).not.toBe('')
}
})最後是用來判斷特殊值的斷言,例如 undefined 、 null 、 true 等等:
test('Special value',()=>{
//期望值為 true
expect(true).toBeTruthy()
//期望值為 false
expect(false).toBeFalsy()
//期望值為 null
expect(null).toBeNull()
//期望值為 undefined
expect(undefined).toBeUndefined()
//期望值為 undefined 之外的值
expect(null).toBeDefined()
})產生覆蓋率報告
在測試的時候,會使用各種斷言來確認結果是否與期望值符合,但是如果測試的內容遺漏了某個條件分支,便無法確認該分支的邏輯性是否正確。
覆蓋率就是用來統計被測試的函式,程式碼的執行比例,當函式內所有程式都被測試過,那覆蓋率就會呈現 100% 。
另外, Jest 內建的覆蓋率會以每個 JavaScript 檔案統計,因此在產生前還需要將「函式」與「測試」檔案分開,如下:
建立 ./funcs/func.js 放要測試的函式:
const sum = (a, b) => {
if (b)
return a + b
else
return a
}
module.exports = {
sum: sum
}func.js 中的 sum 有兩個分支,一個是在 b 有值的時候回傳 a + b ,另一個是在 b 沒有值的情況下直接回傳 a ,最後使用 module.exports 將 sum 匯出。
接著建立測試檔案 ./test/index.test.js :
let func = require('../funcs/func.js')
test('test sum',()=>{
expect(func.sum(2)).toBe(2)
})最後到 package.json 中在 test 的指令後加上 —coverage ,讓 Jest 執行完時同步產生測試報告,當然也可以另外設定新指令:

執行測試後,除了測試結果外,還會產生覆蓋率統計資訊:

也可以發現在專案目錄下多了一個叫做 converage 的資料夾,裡面的 Icov-report 內有個 index.html ,打開後也可以看到相同的資訊:

透過點擊 func.js 可以確認更詳細的測試過程,包含是哪一行沒有執行,或每行各執行了幾次:

現在為 ./funcs/func.js 內的 sum 增加一個斷言句,讓 Jest 在測試時進入 sum 的另一個分支:
let func = require('../funcs/func.js')
test('test sum', () => {
expect(func.sum(2)).toBe(2)
//增加斷言
expect(func.sum(2, 3)).toBe(5)
})執行測試後再一次點開 converage 內的 html 測試報告:

func.js 的詳細執行資訊,在判斷 b 是否有值的地方執行了兩次,且已經沒有還未覆蓋的區塊:

本文解釋了基本的 Jest 測試,並簡單描述斷言庫和產生覆蓋率報告,今後的文章會繼續解釋 Jest 提供的其他功能,一直到用於測試 React 的 Component 組件。
如果文章中有任何問題,或是不理解的地方,都歡迎留言告訴我,謝謝大家!
參考文章