iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 23
4
Modern Web

在 React 生態圈內打滾的一年 feat. TypeScript系列 第 23

Day22 | 創建假 History ,騙過真 Router

前言

來到測試的最後一個章節了,本篇要說明的是如何對 React-Router 做測試,確認 Component 在不同的 Router 的 Render 狀況,因此主要處理的部份在模擬 Router 的 History,用假的 History 觸發 Route Render Component。


前置準備

  1. 文中的專案會以 Day21 的專案架構繼續講解,如果未跟到前一天的進度,可以從 GitHub 上 Clone 下來。
  2. 一顆擁有學習熱忱的心。

使用方法

下載 history

為了輕鬆模擬出 history,我們可以使用 history 框架替我們處理 Url 的紀錄:

npm install history --save-dev

整理 Main

因為本篇會使用 Main Component 進行測試,目前 Main 所在的位置是 src/index.jsx,但我們可以將 Main 抽出來,把它放到 src/Component 內,像是這樣子:

|-src
 |-component
  |-Main
   |-index.js
   |-Main.jsx

取出來後 src/component/Main/Main.jsx 的內容會變成:

同目錄下的 index.js 記得要將它 export。

另外抽出 Main 後,src/index.jsx 會只剩下 ReactDom.render 成為單純的進入點:

整理完後就能開始測試了!

撰寫測試

第一步仍然是先建立測試檔案:

|-__tests__
 |-component
  |-Main
   |-Main.test.js

完成測試的起手式做出來,本次要測試的內容為點擊待辦事項的連結後,首頁的 Content 是否會消失,然後 TodoList 有沒有正常 Render:

這裡大家需要注意一件事情,因為 Main 被 Render 後,在 Main 下的 TodoList 以及 Content 也都會被 Render 出來,這時候就會遇到一個問題,那就是他們兩個 Component 都被 Redux 罩著,所以即使 Main 沒使用到 Redux,還是得替它打造 Redux 環境:

然後其實在這裡從 todolist 中取出 Reducer 有點奇怪,怕大家混淆說明一下,因為目前專案裡也沒其他 Reducer,如果有使用 combileReducer 先將 Reducer 統一匯出,就會明瞭的多了,總之在測試環境中搭建 Redux 環境,總是取創建 store 的那個就對了!

處理完 Redux 後還不夠,當初使用 Router 的時候,也有在最外層放置 BrowserRouterHashRouter,但這裡輪不到他們出場,只需使用 Router 就行,它是前面兩種 Router 的底層接口,測試時會直接用它定義 history,除了 Router 外,也一併將 createMemoryHistoryhistory 中取出:

import { Router } from 'react-router-dom'
import { createMemoryHistory } from 'history'

接下來在 generateComponent 內使用 createMemoryHistory 創建一個假的 history,並將它用 Props 放到 Router 中,而 Router 的位置一樣是放在 Provider 的下一層,像這樣:

完成後可以先 Render 出來,確認目前被 Render 出來的 Component 是不是 ContentContent 內的 data-testid 前幾篇已經添加過了了:

既然有了斷言,就能先進行測試看看:

看來畫面很正常,目前 Content 好好的被 Render 在畫面上,接下來要能夠取得待辦事項的連結,打開 Main,並在兩個連結上添加 data-testid

加上 data-testid 後就可以用 fireEvent 點擊連結,以下先斷言點擊後 Content 是不是消失了:

執行測試後會發現出現一串錯誤,以下列出重點訊息:

錯誤訊息說明因為 Content 已經消失了,所以 getByTestId 找不到它就會報錯,要解決這個問題很容易,只需使用 queryByTestId 代替 getByTestId 就行了,因為 queryByTestId 只要找不到對應的 date-testid 則是會回傳 null,因此在斷言中使用 null 判斷有沒有 Render 就好,修改過後測試案例會變成:

測試也能正常通過,但這個測試案例的最終目標是去認 TodoList 是否有正常 Render,因此打開 src/component/TodoList/TodoList.jsxTodoList.jsx 加上 data-testid

最後就能在 Main_ClickTodoLisstLink_RenderTodoList 中斷言 TodoList 了,而其他的斷言避免過度指定,就可以先拿掉,最後測試案例會長這樣子,也好好地符合測試案例的名稱:

測試結果當然也是正常的:

本文的範例程式碼會提供在 GitHub 上,歡迎各位參考:)


結尾

關於 React 測試的所有技巧都已經在這幾個篇章內提到了,雖然還沒有全部都寫完,但是剩下的應該可以讓大家練習一下,如果不知道從何寫起,可以參考 npm run test-cov 產生的覆蓋率,如果在學習測試的過程中有遇到任何問題,都歡迎提出來討論。

最後也推薦 @testing-library/react 作者的 Blog,相信大家可以學到很多!

如果文章中有任何問題,或是不理解的地方,都可以留言告訴我!謝謝大家!


上一篇
Day21 | 從測試角度操作 Redux-Saga 和 Reducer
下一篇
Day23 | 你説 JS 是什麼弱型別? TypeScript 強勢登場
系列文
在 React 生態圈內打滾的一年 feat. TypeScript31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

2
連城
iT邦新手 4 級 ‧ 2020-07-08 16:46:16

更新一下 卡在這邊有點久了
下方提出的缺陷後來發現其實是後面會加上 所以可以不用理

但是jest 在fireEvent上有出現BUG 但作者疑似太忙無法立即修復
所以建議先跳過最後一個test

src/component/Main/Main.jsx
檔案中缺少data-testid="contentBlock

import React from 'react';
import { Switch, Route, Link } from 'react-router-dom';
import Content from '../Content';
import TodoList from '../TodoList';
import List from '../List';

const Main = () => (
  <div>
    <ul>
      <li>
        <Link to="/" data-testid="homeLink">
          首頁
        </Link>
      </li>
      <li>
        <Link to="/todolist" data-testid="todolistLink">
          待辦事項
        </Link>
      </li>
    </ul>
    <Switch>
      <Route exact path="/" component={Content} />
      <Route path="/todolist" component={TodoList} />
      <Route path="/list/:taskName" component={List} />
    </Switch>
  </div>
);

export default Main;

會導致斷言失敗

    describe('Main',()=> {
    test('Main_ClickTodoListLink_RenderTodoList',()=>{
        const { getByTestId } = generateComponent(<Main />) 
        expect(getByTestId('contentBlock')).toBeInTheDocument()
    })
})
神Q超人 iT邦研究生 5 級 ‧ 2020-07-18 22:22:46 檢舉

感謝!我在把它補到正確的位置中!
話說我覺得你非常會除錯,然後我感覺很不好意思 /images/emoticon/emoticon25.gif
看著我的文章辛苦了 XD

連城 iT邦新手 4 級 ‧ 2020-07-22 13:58:52 檢舉

不用介意
既然實作了就會確保我每個commit都要可以pass
避免我之後的人再踩到

我要留言

立即登入留言