如何針對 Component 寫出務實(practical)的 test

react 裡 component 的特性

專案情境描述

CRA 的測試在開始執行之前做了什麼

要在哪裡 mock 特定的 WebAPIs

使用合適的 test helper library

做好一個 custom render 的 util function

在上面的影片中 Kent 先寫了一個 custom render 的 util function,這是一個很重要的關鍵,儘管影片中是搭配 react 16.8 之後的 hook APIuseContext 使用,但是概念是一樣的,我們要測試的 Component 往往會連結 react-redux 的 Provider 裡的 store ,在為這個 Component 進行單元測試或是整合測試時必須要先為它準備好這樣的環境。

以下是一個 react 寫成的 todoList 的範例,host 在 CodeSandbox 上,

在使用 react-testing-libraryrender function ,把要被測試 Component render 到 jsdom 之前,我們連 react-reduxProvider 一起 init,並且把外部傳進來要塞進去 store 裡的假資料一起交給 redux ,搭配我們的 reducers,一起組成 store

在測試執行的時候我們就能夠直接把假的資料直接傳到我們的 helper function 裡,受測試的 Component 得到 由 mapStateToProps 轉化而成的 Props 裡的值,而直接 render 出畫面。

針對 middleware 那裡的操作是排除的。因為那些地方的測試會由針對 action creator 的測試去執行,這樣才夠單元...

一個 test case

上面的單元測試中我們可以先注意幾個地方:

  1. 第 7 行 import 了 jest-dom/extend-expect ,它是 test-library 團隊出的,目的是為了要讓執行測試時的 jest 有更多的assertfunction 可用

  2. 第 18 行在每個 describe 之後都會發動 cleanup ,應該是為了清除 testing-library 自己內部的狀態之類的

  3. 第 20 行 跟 第 35 行各是一個 test,每次都從新準備假資料,從新發動我們事先準備好的 renderReduxConnectedHOC ,每次 describe 裡都是在生出一組新的 Provider & Store,這也是 Kent C. Dodds 在影片中強調的,要保持每次測試的獨立性- test isolation with react ,確保上一次的測試不會影響到下一次的測試,不然可能只是越測越亂。

  4. renderReduxConnectedHOC 會返回一個由 react-testing-libraryrender 生成的 container,在之後我們就可以用它的各種 queriesAPI 去選取元素,準備去做 expect,因為我們的 Component 是用 StyledComponents 產生出來的,它產生出來的 Component 是沒有固定的 css class 的,所以我們無法用普通的 css selector 去抓到我們想要比對的元素,所以我們在要抓取的 dom 的地方,在 render 時,讓它 render 出 data-testidreact-testing-library 有對應的能透過 data-testid 去抓到元素的 queryAPI ,在第 29 行,第 48 行即使用這樣的方式。

  5. data-testid在要釋出正式版的程式碼時可以透過 babel-plugin-react-remove-properties去消除,可能在develop跟production的時候要使用 不同的.babelrc

UI 是資料的映射

今日的 web UI framework 的設計理念都是想要做到UI是資料的映射,資料一變動,UI就跟著變動,就像 用 Excel spreadsheet 函數在做資料的計算那樣,所以 react 真的很 react(UI 一直在反應著資料)。 Excel-react

在上面的 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_testBtn

這個 CodeSandbox 的環境是可以直接執行上面提到的測試的,按下上圖紅框匡起來的按紐就能執行測試(超神奇!),請務必試試看。