Jest | 再一次測試你的 Component - feat.react-testing-library 基本用法

神Q超人
Enjoy life enjoy coding
9 min readMay 9, 2019
用 react-testing-library Render Component 為 DOM 做測試

前言

Hi !大家好,雖然之前有使用 Enzyme 講解如何搭配 JestReact 的Component 做測試,但是幾個禮拜前偶然在某個討論串中看到有大神推薦另一套測試 Component 的套件 react-testing-library ,功能和 Enzyme 相同,兩者都是在測試時 Render Component 的 DOM 下斷言測試,如果是剛接觸 Enzyme 的朋友,不妨可以參考看看兩者的不同,來選擇愛用套件 😃 。

react-testing-library

SUT (測試目標)

在開始測試之前,仍然需要一個小助手,這裡請出之前常露面的 Counter 來擔任 SUT:

安裝 react-testing-library

因為只有在開發時的 Test 上用得到套件,因此安裝在 devDependencies 裡:

npm install --save-dev react-testing-library

6 / 16 更新:要注意哦! react-testing-library 似乎在版本 8 的時候將套件換成 @testing-library/react 了,目前筆者還不曉得差異在哪裡,使用來也沒有感到差別,所以如果文章中有問題再麻煩留言告知,感激不盡!

//新版本:
npm install --save-dev @testing-library/react

常用 API

撰寫測試前,先簡單說明幾個常用的 API :

render

react-testing-library 的 render 類似於 Enzyme 中的 Mount ,意思是它會將所有的子組件都 render 出來成為 DOM 節點。

getByTestId 、 getByText

render 後會回傳的 Method ,兩個都是用來搜尋 DOM , getByTestId 是以 DOM 中的 data-testid 值取要斷言的 DOM , getByText 則是以該 DOM 內呈現的內容,獲取到 DOM 後便能以 textContent 再取得內容。

container

container 也是 render 所回傳的,等於取得整包 DOM 物件,甚至是能夠直接對它使用 query​Selector 來搜尋節點,通常我會在搞不清楚到底 Render 了什麼的時候,用 innerHTML 來偷看 😆。

fireEvent

這個 Method 可以觸發 DOM 的事件,例如 onClickonChange 等等。

開始測試

其實只要了解上述幾個簡單的 API ,就能夠輕鬆對 Component 的節點做測試,下方先 renderCounter ,並對 span 的內容做斷言,確認是否 render 正確:

上方用了三種方式去取得 span 來確認 Component 顯示的是否正確, getByTestId 就是直接抓取相同 data-testid 的 DOM,然後如果不幸有兩個 DOM 同時用了一樣的 data-testid 那測試就會發生錯誤:

顯示找到複數的錯誤

但錯誤的提示還滿不錯的,如果真的必須要取同樣的名稱,也可以用 getAllByTestId ,來獲得一個陣列,當然它也是由 render 回傳的 Method 之一。

第二種方式是以 DOM 的內容來獲取,因為一開始 CounterStatecount 值是 0 ,所以可以知道 span 的內容會是 點了0下 ,這個 Mtehod 可以用在內容不會改變的地方,像是登入按鈕的字就永遠是登入,儲存就永遠是儲存,不會有變化,這種情況就能用 getByText

第三種的 container 就把它當成 JavaScript 取得的物件就好,但通常不會使用它來查找 DOM ,但為什麼?不是很方便嗎?這個是有原因的,文章的最後會整理一些結論。

接著,要來確認的是按鈕的事件,點擊時會不會讓 count 值加上一或二,這時候就能用到剛剛提及的 getByText 因為按鈕的文字是不會改變的,找到按鈕後就能使用 fireEvent 觸發點擊:

如果是 change 也是一樣的方式,只需將改變的值放在 fireEvent 的第二個參數,要注意的是改變的值仍然要模擬觸發事件本身的 event

fireEvent.change(input, { target: { value: '2', }, });

測試結果如下:

感覺上寫起來是不是直觀許多了?而且在這之前一直故意不去提,不曉得大家有沒有發現,上述的 Counter 是透過 Hooks 的 useState 管理 State ,也就是說 react-testing-library 百分之百完美相容 Hooks ,測試起來絕對不會有問題!

顯然這是個令人開心的消息,但 Redux 呢?會不會相對變得複雜?

你的考量,我看得見!

下方就持續講解該如何在 Redux 專案中進行測試!

Redux

這裡先將小助手 Counter 申裝上 Redux :

也少不了 Redux 的標配 actionsreducerstore

開始測試

欸等等?不需要再裝些什麼其他 redux-mock-store 嗎?

完全不用

貫徹 Mount 的精神,就直接用正式的 Reducer 正式的 store 來測試,把覆蓋率蓋好蓋滿,以下會示範三種在 Redux 中玩轉測試的方法。

直接使用 Reducer 創建 Store

render 的時候就直接帶入 Store 了,而且例子中有特別印了關於 Store 的兩段 console.log ,可以清楚看見 Store 會隨著測試改變 State 的值:

Store 的值會隨著測試的過程改變

如此一來也能更方便的知道,觸發某個 dispatch 後, Store 內的 State 變化是不是在預料之中。

指定 Reducer 的預設值

除了用原有的 Reducer 外,也能另外指定 State 取代 Reducer 自身的初始 State :

自訂 renderWithRedux

這個 render 方式不是 react-testing-library 原有的 Method ,而是官方用了一些小技巧另外寫的,它長這樣子:

看起來有點複雜,但其實內部的原理就是將上方例子 render 的步驟簡化成 Method ,回傳的結果也和 render 後的 Component 一樣,只是會多傳一個 Store ,實際用起來如下:

筆者也建議可以直接使用 renderWithRedux ,讓測試的畫面看起來比較乾淨俐落,不會定義一堆重複的東西,上方關於 Redux 的例子也都會改成 renderWithRedux 重新寫在筆者的 GitHub 上,可以再參考看看。

使用心得

react-testing-library 的基本方法大家都應該了解了,最後就來談談使用的心得。

一開始最困惑的點是 getByTestIdgetByText ,根本就不曉得到底為什麼要這樣子做,因此大量了使用 container 搭配 querySelector 抓取想斷言的 DOM ,初期用得很開心,但是最後突然發現,如果節點的位置發生改變,或多了另一個 DOM ,都有可能會讓 Test Case 錯誤,但其實不是錯在邏輯,而是因為原本的 querySelector 已經取不到更改前的 DOM 。

這裡 react-testing-library 的開發者 Kent C. Dodds ,也有在他的 Blog 寫了一篇文章提出對 UI 面對測試時的看法

文章裡有個最簡單的例子,當假設 Component 中有一個按鈕:

<button className="button_style" type="button">點我</button>

那 Test Case 會這樣子得到它:

container.querySelector('button[class="button_style"]')

看起來一切正常,但是當對 Component 做了異動:

<div>
<button className="button_style" type="button">想不到吧</button>
<button className="button_style" type="button">點我</button>
</div>

原本 Test Case 中的 querySelector 就取成第一個新增的按鈕,而不是原有的 點我 按鈕。

再來,若是有天 button 們都不再需要依賴 button_style 這個樣式,那是否應該要為了 Test Case 而將這沒有任何用的 ClassName 屬性留下?答案應該很明顯。

因此,提升 UI 在測試時的適應力非常重要!

如果考量到 data-testid 被 build 後會被看見,也可以透過 babel-plugin-react-remove-propertiesdata-testid 移除。

本文用了一些例子講解 react-testing-library ,因為筆者大約是今年三月多才開始玩測試,所以 Enzyme 及 react-testing-library 體感上會偏好使用後者,因為寫起來還滿方便的而且又支援 Hooks 😅,不過如果有不同看法,也歡迎提出一起討論!

最後對於文章中講解有任何不清楚或是覺得有需要補充的地方,再麻煩留言指教,謝謝!

參考文章:

  1. https://github.com/testing-library/react-testing-library
  2. https://kentcdodds.com/blog/making-your-ui-tests-resilient-to-change

--

--