props
, HOC
, renderProps
這些設計和技巧都讓人很有依賴注入的感覺。test runner
就是 jest使用 jsdom 在 node.js 的 JavaScript runtime 底下模擬出一個假的瀏覽器
因為是假的
,所以有些 Web API
是不存在的,如 canvas
var canvasDomNode = document.getElementById('tutorial')
var ctx = canvasDomNode.getContext('2d')
// ^^^ Uncaught TypeError: canvasDomNode.getContext is not a function
如果 component 裡有用到這些 API 的話,就必須想辦法做出一個替代品,讓它在能夠取代在執行測試時不存在的 API(俗稱 mock
)
JSDom 在記憶體中建構出像是 DomTree 的東西,但是不會畫出畫面來(也沒辦法畫),因為不會畫出畫面,所以測試流程就能執行得比較快
透過 jest
去執行我們寫好的測試檔(describe
, it
, expect
), jest
會自動在專案底下找檔名是符合特定格式的檔案來執行(xxx.test.js or xxx.spec.test ) @see CRA doc
CRA
在它的 test
指令裡都幫我們處理好了,感謝 CRA
! # in pacaage.json file
# ...其他設定
"scripts": {
# ......其他 npm script
"test": "react-scripts test --env=jsdom",
# 更多 npm script
}
所以執行 yarn run test
時就會執行測試
package.json
檔案裡,可以加上一個 jest 的區塊,來 config jest,@see
# in package.json file
jest {
# ...設定
}
其中有一個屬性 setupFiles 可以用來指定在 jest
開始執行測試前要先執行過哪些檔案,但是這個屬性在 CRA
底下沒辦法使用。
所幸 CRA團隊
有留一個後門,只要在 src
底下寫一個 setupTests.js
, react-script
在執行測試前會先去執行過裡面的 js,我們就能夠在裡面做要先 mock 好的事情。 @see CRA_Issue6020
使用合適的 test helper library 來幫我們處理 render
,
透過某些方式選取到指定的節點
, 做assert
這些都是好的 test helper library 能幫助我們的,
之前在 react 社群中很流行使用 Airbnb公司
開源的 enzyme
。
但是這邊我們要選擇另外一套目前在 react 社群中越來越火的, react-tesing-library ,它是在 react 社群中有名的 influencer Kent C. Dodds
所推出。
他在他的部落格和 youtube頻道中都介紹到很多很有用的 react 開發秘訣,本次我們也會參考他 youtube頻道中的 Testing a React component that uses useContext
這段之前的直播影片,來先寫出一個能夠搭配Redux
使用的 test util function。
在上面的影片中 Kent 先寫了一個 custom render 的 util function,這是一個很重要的關鍵,儘管影片中是搭配 react 16.8 之後的 hook API
的 useContext 使用,但是概念是一樣的,我們要測試的 Component 往往會連結 react-redux 的 Provider
裡的 store
,在為這個 Component 進行單元測試或是整合測試時必須要先為它準備好這樣的環境。
以下是一個 react 寫成的 todoList 的範例,host 在 CodeSandbox 上,
在使用 react-testing-library
的 render
function ,把要被測試 Component render 到 jsdom 之前,我們連 react-redux
的 Provider
一起 init,並且把外部傳進來要塞進去 store
裡的假資料一起交給 redux
,搭配我們的 reducers,一起組成 store
在測試執行的時候我們就能夠直接把假的資料直接傳到我們的 helper function 裡,受測試的 Component 得到
由 mapStateToProps
轉化而成的 Props 裡的值,而直接 render 出畫面。
針對 middleware 那裡的操作是排除的。因為那些地方的測試會由針對 action creator
的測試去執行,這樣才夠單元
...
上面的單元測試中我們可以先注意幾個地方:
第 7 行 import 了 jest-dom/extend-expect ,它是 test-library
團隊出的,目的是為了要讓執行測試時的 jest 有更多的assert
function 可用
第 18 行在每個 describe
之後都會發動 cleanup
,應該是為了清除 testing-library 自己內部的狀態之類的
第 20 行 跟 第 35 行各是一個 test,每次都從新準備假資料
,從新發動我們事先準備好的 renderReduxConnectedHOC
,每次 describe
裡都是在生出一組新的 Provider & Store,這也是 Kent C. Dodds 在影片中強調的,要保持每次測試的獨立性
- test isolation with react ,確保上一次的測試不會影響到下一次的測試,不然可能只是越測越亂。
renderReduxConnectedHOC
會返回一個由 react-testing-library
的 render
生成的 container,在之後我們就可以用它的各種 queriesAPI 去選取元素,準備去做 expect,因為我們的 Component 是用 StyledComponents 產生出來的,它產生出來的 Component 是沒有固定的 css class
的,所以我們無法用普通的 css selector 去抓到我們想要比對的元素,所以我們在要抓取的 dom 的地方,在 render 時,讓它 render 出 data-testid
, react-testing-library
有對應的能透過 data-testid
去抓到元素的 queryAPI ,在第 29 行,第 48 行即使用這樣的方式。
data-testid
在要釋出正式版的程式碼時可以透過 babel-plugin-react-remove-properties去消除,可能在develop跟production的時候要使用 不同的.babelrc
今日的 web UI framework 的設計理念都是想要做到UI是資料的映射,資料一變動,UI就跟著變動
,就像
用 Excel spreadsheet 函數在做資料的計算那樣,所以 react 真的很 react(UI 一直在反應著資料)。
在上面的 test case 中第一個 describe 是測試可以 render 得出一個個的 todoItem,所以給三筆 todo 資料,畫面上就必須要 render 出三筆 todo(用 data-testid 去抓取,然後比對抓到的 nodeList 的長度),簡單明暸。
再來就是這個 TodoList App 有一個 Highlight todo 的功能,click 了 Highlight 按鈕之後,該 todo 會改變顏色,然後跑到 List 區的最上端。如果我們想測,點擊了之後會 Highlight todo
這件事情,該怎麼寫測試?
我們先思考,一個 todo 怎麼會知道它是被 Highlight 的?畫面上是怎麼知道它必須被 Highlight?
那是因為每個 todo 資料裡會有一個 highlighted
屬性,click Highlight button 這件事情本身只是在修改對應的 todo data 裡那個屬性的值,
todoItem 在 render 的時候檢查自己的 highlighted
屬性,
如果是 true
,就 render 成 被 highlight 時該呈現的樣子,
如果是 false
就 render 回 沒有 被 highlight 時的樣子。
所以該測的不是 click
這件事,應該是要測試給了幾筆 highlighted 是 true 的資料,對應 render 出來的有被 highlight 的資料該有幾筆(給不同的 data-testid),沒有被 highlight 的資料應該要有幾筆,因為 UI 是資料的映射,UI 會反應資料,資料一變動,UI 應該要跟著起變動。
click 通常只是發送一個 action 去到 Reducer 那裡,這件事情可以在針對 action creator
的單元測試裡處理(測試該 action creator function 是否 return 出正確的 action object),而按鈕能否點擊(比如說是不是有綁好 even-handler,或是上面是否有蓋到一層 z-index 比下層還高的透明區塊造成下面被覆蓋的區塊點擊不到)這件事可以移到 e2e
測試中,寫自動化測試 script 去實行。
但是如果你真的想透過 react-testing-library
在寫的測試程式裡去執行 click
, change
之類的事件的話,其實也是可以做得到的。 @see fireEvent
這個 CodeSandbox 的環境是可以直接執行上面提到的測試的,按下上圖紅框匡起來的按紐就能執行測試(超神奇!),請務必試試看。