Jest | 經過測試,讓你的組件安全有把關 shallow render 篇 - feat.React, Enzyme

神Q超人
Enjoy life enjoy coding
9 min readApr 7, 2019

前言

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 的版本對應表:

Adapter 與 React 的版本對應表

這裡直接安裝最新的 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 namecount

開始測試

就算是測試 Component 也是使用副檔名 .test.js 因此在根目錄下建立一個測試檔案,並將 ReactEnymeAapter 及受測的組件 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 中的 h1p 的話,能夠直接以下方選擇器取得:

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 產生的覆蓋率報表確認漏掉哪裡:

Jest 自動產生的覆蓋率報表

原來是 inputonChange 事件沒有測試到,這個部分可以透過第四個測試案例,將 setState 改為用 simulate 模擬 inputonChange 事件驅動 State 改變,而這裡也接觸到了一個關於 Unit Test 的核心:

測試案例不只是能夠確認 Function 的邏輯性是否正確就行了!

測試案例重視的應該是以使用者的操作模式下去思考 Function 的使用情境,就如同上方的例子來說,雖然直接以 setState 也能夠測試 Render 的畫面會不會同步變動,但是操作上使用者根本不可能直接接觸到 setState ,而是透過填寫賦予 inputonChange 去觸發 setState ,因此在撰寫第四個測試案例時,本來就應該以觸發 onChange 事件為主,以下就將 setState 改為觸發 inputonChange

可以發現和 Click 不同的是, Change 多了一個參數,這個參數會傳進 onChange 的事件中作為它的參數使用,而這裡要另外傳的原因是因為 simulate 只是模擬執行 onChange 事件而已,並不是真的在 input 中輸入文字而觸發 onChange ,所以如果不指定的話,原本 e 參數的 target 就會是 undefined 而不是 input ,也就取不到 value 值無法順利更新 State。

再執行一次測試,就能發現覆蓋率已達 100 % :

shallow 淺渲染

shallow 放到最後講實在是有點奇怪,但還是這麼做了,其實上方所使用 Render Component 的方式都是叫做「淺渲染」,也就是說透過 shallow Render 出來的 Component 不會有斷言到子 Component 的部分,例如:

這時候測試時,如果是透過 shallow Render ,那將無法取得 Titlep 標籤內容:

淺渲染只會 Render 當前的 Component

這麼做的好處一樣在關注點分離上面,如果要測試 Title 就另外讓 Title 成為 SUT ,而不是在 Counter 中斷言 Title 的內容。

另外與 Shallow Rendering (淺渲染)相對應的是 Full Rendering ( 完整渲染 ),在 Enzyme 中 Full Rendering 的 Function名稱是 mountmount 會把該 Component 中所有子 Component 也都 Render 出來,也就是說如果上方的測試案例改用 mount

那麼測試就會通過:

打到最後才發現,其實想說的東西還有很多,但是如果全部都擠在這篇文章,反而會顯得太擁擠,因此決定再多開幾篇來提及更多關於 Full Rendering 及其他在測試 Component 時使用 Mock 技巧,而單就本文而言也將 Shallow Rendering 的基本用法都整理上,希望之後能夠讓 Jest 章節更完整,如果對於文章中講解有任何不清楚或是覺得有需要補充的地方,再麻煩留言指教,謝謝!

參考文章

  1. https://github.com/airbnb/enzyme
  2. https://jestjs.io/docs/en/tutorial-react#enzyme

--

--