Jest | 經過測試,讓你的組件安全有把關 shallow render 篇 - feat.React, Enzyme
前言
Jest 篇章一路打到這裡,總算是處理到 React 的 Component 了,剛開始學測試的原因也是為了 React 和目前正在研究的重構,之後的目標也會將重構的學習心得整理成文章與大家分享,但在那之前還是得將 Unit Test 的最後一哩路走完 💪 !
Enzyme
在之前的文章中,單單使用 Jest 中的斷言庫來測試一般 Function 的邏輯就足夠了,但是 Component 中不只會有邏輯,還有 Html 的結構、 Props 的值、管理的 State 、甚至是 Click 或 Change 事件要執行等等,因此在這裡筆者選擇使用 Enzyme 搭配 Jest 為 Component 做測試。
Enzyme 本身並沒有提供測試的功能,但它能夠將 Component 給渲染,並在渲染出的 Element 間取得屬性或移動節點,也就是說,在測試 Component 的過程中,只需使用 Enzyme 將 Props 或執行動作前後的 State 值取出,再藉由 Jest 提供的斷言庫判斷有沒有符合期望值,便能確認 Component 的運作是否如預期。
這裡有一點特別的事項需要注意,因為 Hooks 目前剛申裝上 React 幾個月的時間而已, Enzyme 還沒有完全支援使用 Hooks 的 Function Component ,以致在測試 Function Component 時無法取得 State ,因此本篇的文章都會使用 Class Component 作為測試例子。
不過目前 Enzyme 開發者已經有開啟 issue 準備處理 Hooks 的 API ,相信在一些時間就能獲得解決。
安裝 Enzyme
第一步要從 npm 中安裝 Enzyme :
npm install enzyme --save-dev
但除了 Enzyme 外還要安裝 Adapter (解析器), Adapter 依賴著 react 和 react-dom ,會根據不同的 react 版本而有不同的 Adapter ,以下是官方 GitHub 的版本對應表:
這裡直接安裝最新的 enzyme-adapter-react-16
:
npm install enzyme-adapter-react-16 --save-dev
設置 Jest
之前的文章都是直接以 JavaScript 的 Function 做測試,但是 React 的 Component 並不是一般的 JavaScript 語法,而是 JSX ,所以再測試時需要再經過編譯,編譯的套件使用 Babel ,下方會列出所有相關套件:
npm install babel-jest @babel/core --save-dev
npm install @babel/preset-env @babel/preset-react --save-dev
第一行的 babel-jest
是執行測試時的預處理器,另外 @babel/core
為 Babel 的核心套件。
第二行的 @babel/preset-env
和 @babel/preset-react
是 Babel 編譯時會依照這些 Loader 去轉換語法,前者編譯 ES6,後者編譯 JSX 。
下載完後再專案的目錄下新建一個 Babel 的設定檔 .babelrc.js
,測試時會依照這個設定檔下去編譯程式碼,內容如下:
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
}
測試 Component
測試之前,先打造一個受測 Component ,下方是一個簡單的計數器:
Counter
會 Render 出幾個 Element ,主要操作的是輸入姓名的 input
、顯示 State 的 span
、增加點擊數量的 button
三個,待會測試的對象也會圍繞著這三個和管理資料的 State name
及 count
。
開始測試
就算是測試 Component 也是使用副檔名 .test.js
因此在根目錄下建立一個測試檔案,並將 React
、 Enyme
、 Aapter
及受測的組件 counter
通通 import 到文件中:
確認 State
進行測試時,得先將 Component 以 shallow
做 Render ,並將結果交給變數 counter
,完成後便可直接對 counter
做操作:
上方的測試案例使用 .state('key')
取得目前 Component 中對應 key
的 State 值,取得後使用 Jest 中的 expect
為它做斷言。
執行事件
在 Enzyme 中找尋 Element 的方式有點像 JQuery ,透過 .find
加上選擇器找到目標 Element ,並對它使用 simulate(event)
執行事件:
放在 .find
中的選擇器就像 JQuery 一樣, className
可以用 .
; id
以 #
尋找,例如下方的例子:
若要取得 Header
中的 h1
及 p
的話,能夠直接以下方選擇器取得:
const Header = shallow(<Header />)// 取得 id 為 bigTitle
Header.find('#bigTitle')// 取得 class 為 describe
Header.find('.describe')
但是,如果是使用 class
選擇器取 Element ,可能會找到多個節點,這時候能以 .first()
指定取第一個符合的或是 .at(index)
控制要取第幾個符合的 Element ,參數 index
從 0 開始。
確認 Element 的內容
網頁顯示的畫面除了 Element 外,還有在它之中的內容,例如在 Counter
中就是利用 span
的內容顯示目前誰點了幾下,在 Enzyme 中尋找到 Element 後能直接以 .text()
取得該內容:
改變 State 的值
在 Component 中, Render 出的內容可能會因 State 而有所不同,因此在測試時,也能手動 setState
,確認在 State 改變時,畫面會不會跟著變動,以下改變 State 中 name
的值,在判斷對應的內容是否改變:
上方使用到了 .props()
,它能夠獲取指定 Element 的所有 Props ,例如 input
中的 value
就是其中一個 Props ,除了上方的用法外,也可以直接指定 Props 的名稱回傳值:
counter.find('div').find('input').props().value
//等同於
counter.find('div').find('input').prop('value')
最後執行測試,查看上方四種測試案例是否都能夠 Pass :
查看覆蓋率
測試結果是通過的,但有 Counter
的 Function 覆蓋率只達到 75 % ,這時候可以點開 Jest 產生的覆蓋率報表確認漏掉哪裡:
原來是 input
的 onChange
事件沒有測試到,這個部分可以透過第四個測試案例,將 setState
改為用 simulate
模擬 input
的 onChange
事件驅動 State 改變,而這裡也接觸到了一個關於 Unit Test 的核心:
測試案例不只是能夠確認 Function 的邏輯性是否正確就行了!
測試案例重視的應該是以使用者的操作模式下去思考 Function 的使用情境,就如同上方的例子來說,雖然直接以 setState
也能夠測試 Render 的畫面會不會同步變動,但是操作上使用者根本不可能直接接觸到 setState
,而是透過填寫賦予 input
的 onChange
去觸發 setState
,因此在撰寫第四個測試案例時,本來就應該以觸發 onChange
事件為主,以下就將 setState
改為觸發 input
的 onChange
:
可以發現和 Click 不同的是, Change 多了一個參數,這個參數會傳進 onChange
的事件中作為它的參數使用,而這裡要另外傳的原因是因為 simulate
只是模擬執行 onChange
事件而已,並不是真的在 input
中輸入文字而觸發 onChange
,所以如果不指定的話,原本 e
參數的 target
就會是 undefined
而不是 input
,也就取不到 value 值無法順利更新 State。
再執行一次測試,就能發現覆蓋率已達 100 % :
shallow 淺渲染
把 shallow
放到最後講實在是有點奇怪,但還是這麼做了,其實上方所使用 Render Component 的方式都是叫做「淺渲染」,也就是說透過 shallow
Render 出來的 Component 不會有斷言到子 Component 的部分,例如:
這時候測試時,如果是透過 shallow
Render ,那將無法取得 Title
的 p
標籤內容:
這麼做的好處一樣在關注點分離上面,如果要測試 Title
就另外讓 Title
成為 SUT ,而不是在 Counter
中斷言 Title
的內容。
另外與 Shallow Rendering (淺渲染)相對應的是 Full Rendering ( 完整渲染 ),在 Enzyme 中 Full Rendering 的 Function名稱是 mount
, mount
會把該 Component 中所有子 Component 也都 Render 出來,也就是說如果上方的測試案例改用 mount
:
那麼測試就會通過:
打到最後才發現,其實想說的東西還有很多,但是如果全部都擠在這篇文章,反而會顯得太擁擠,因此決定再多開幾篇來提及更多關於 Full Rendering 及其他在測試 Component 時使用 Mock 技巧,而單就本文而言也將 Shallow Rendering 的基本用法都整理上,希望之後能夠讓 Jest 章節更完整,如果對於文章中講解有任何不清楚或是覺得有需要補充的地方,再麻煩留言指教,謝謝!
參考文章