iT邦幫忙

2021 iThome 鐵人賽

DAY 29
1
Modern Web

React 前端工程師的兩把刷子系列 第 29

[Day29] React Testing Library 的一些實用的小技巧

今天來談談 React Testing Library 中筆者常用到的一些功能,React Testing Library 的套件名是 @testing-library/react,它是奠基在 Testing Library 提供的許多方法上,為 React 的測試提供了更多不同的方法。如果讀者寫的不是 React,Testing Library 本身也搭配很多不同的框架可以用來撰寫測試,例如,Angular、Vue、Svelte、Puppeteer、Cypress 等等。

同樣的,筆者不會說明如何使用 react-testing-library 來撰寫 React 元件的測試,如果有需要的話,推薦可以看 Youtube 上這系列的教學影片 React Testing Library Tutorial

getBy、queryBy、findBy 的使用時機

在 React Testing Library 中提供了三種用來 query DOM 元素的方法,分別是 getByqueryBy、和 findBy,在官方文件中用詳細的表格來說明這三種方法的差異:

About Queries

圖表資料來自官方網站:About Queries @ testing-library/react

但這張表真正想告訴我們的是什麼呢?這裡筆者整理各個方法的使用時機重點如下:

  • 使用 queryBy找不到該元素時不會噴錯,通常是要用來檢查某個元素「不在 DOM 上」時使用
  • 使用 findBy需要搭配 async/await 時,通常該 DOM 元素不是一開始就 mount 在頁面上
  • 使用 getBy:除了上述情況之外,都可以用 getBy,getBy 在找不到該元素時會直接噴錯(throw Error)

除了這三個方法的差異外,再來就是開發者要找的是「單一個元素(xxxBy)」或「多個元素(xxxAllBy)」:

  • 如果使用的不是 xxxAllBy 的方法,但卻找到超過一個以上的元素時會噴錯
  • 使用 xxxAllBy 方法時會回傳的是陣列

如何快速找到特定元素(query role attribute)

由於 Testing Library 很強調用實際使用者的視角來進行測試,因此它會更偏好開發者使用 DOM 上符合 Accessibility 的元素(例如,role、label)、或者是使用者實際上看得的東西(例如、Text、Title)來找到欲進行測試的元素,而不是透過使用者看不到的 class 或 id 來進行 query。

註:不是不能使用 id 來 query 元素,有些時候只能用 id 或直接用 id 會更有效率,只是以偏好來說,Testing Library 更建議使用符合 Accessibility 方法。

但實務上來說,一般的開發者可能並不清楚每個 HTML 元素所對應的 ARIA role 是什麼,這時候有幾個不同的方法可以處理。

使用 Chrome 的開發者工具

第一種方式是使用 Chrome 內建的開發者工具,在 Chrome 的開發者工具,按下「Alt + Shift + P」後,搜尋 show accessibility,接著在 Accessibility 頁籤下的 Computed Properties 中就會顯示改元素的 role 和 name:

react testing library

如此就可以使用 React Testing Library 提供的 getByRole 方法來選到該元素:

// App.test.tsx
import { render, screen } from './custom-testing-library';
import App from './App';

test('can find the specific text in specific DOM', async () => {
  render(<App />);

  // 使用 getByRole 方法
  const heading = screen.getByRole('heading', {
    name: /your current path is \//i,
  });

  expect(heading).toBeInTheDocument();
});

使用 logRole 方法

或者也可以使用 Testing Library 內建的 logRole API。我們只需要把想檢視的 HTML Element 放入 logRole 這個方法中,Testing Library 就會告訴開發者在這個 HTML 元素中有哪些 ARIA 的 role 可以使用。

舉例來說,在測試的檔案中:

// App.test.tsx
import { logRoles, render, screen } from '@testing-library/react';

test('can find the specific text in specific DOM', async () => {
  const { container } = render(<App />);

  // 使用 logRoles 來檢視某個 HTML Element 所包含的 Accessibility Role
  logRoles(container);
  // ...
});

在 Terminal 中就可以看到所有這個 DOM 中的 ARIA role 和對應的 name,例如這裡包含了兩個 role,分別是 bannerheading

react testing library

使用 Chrome Extension

最後一種,應該也是最簡單的方式是直接透過 Chrome Extension,在 5 Tips to Perfect React Testing Library Queries 這篇文章中推薦了兩個好用的 Chrome Extension,分別如下:

Testing Library: which query

Testing Library: which query @ Chrome Extension

這個套件可以直接把要 query 的元素裝成 Testing Library 的寫法後,用點右鍵的方式複製下來:

react testing library

按下複製後,就可以取得下列程式:

screen.getByRole('heading', { name: /your current path is \//i });

Testing Playground

Testing Playground @ Chrome Extension

另一個套件是 Testing Playground,它會在 Chrome 的開發者工具中多一個 Tab,當你選了特定元素後,一樣會出現可以複製的程式碼,除此之外,最下面還會列出和 Accessible 有關的屬性:

Testing Playground

這兩套都可以方便開發者找到想要的元素。

透過 Debug 把 DOM console 出來

screen.debug()

screen.debug() 這個滿實用也蠻多人知道的,基本上就是可以把當前畫面的 DOM 顯示在 Terminal 中:

import { render, screen } from '@testing-library/react';

test('can find the specific text in specific DOM', async () => {
  render(<App />);

  screen.debug();
});

此時的 Terminal 會得到如下的結果:

screen debug

prettyDOM

雖然用 screen.debug() 可以看到目前 DOM 的樣子,但因為它有行數限制,當 Component 轉譯出來的 DOM 很多行時,就沒有辦法看到完整的內容。這時候就可以使用 Testing Library 提供的 prettyDOM 這個方法來把特定的 DOM 元素 console 出來。

為什麼不直接使用 console.log() 就好呢?因為會非常難看。舉例來說,現在我們找到了 heading 這個元素,想要把它 console 出來看一下:

// App.test.tsx
import { render, screen } from '@testing-library/react';
test('can find the specific text in specific DOM', async () => {
  render(<App />);

  const heading = screen.getByRole('heading', {
    name: /your current path is \//i,
  });

  // 使用原本的 console.log
  console.log(heading);
});

這時候 console 出來的內容會像這樣:

prettyDOM

非常難以閱讀實際的 DOM 會長什麼樣,但如果是先用 Testing Library 提供的 prettyDOM 方法後再執行 console,像是這樣:

// App.test.tsx

import { prettyDOM, render, screen } from '@testing-library/react';

test('can find the specific text in specific DOM', async () => {
  render(<App />);

  const heading = screen.getByRole('heading', {
    name: /your current path is \//i,
  });

  // 先使用 prettyDOM 後再 console
  console.log(prettyDOM(heading));
});

這時候 console 出來的結果如下:

prettyDOM

是不是容易閱讀的多了。

在 Production 移除 test-id

最後,除了使用 Accessible Attribute 或畫面上的文字來 query DOM 元素之外,有時還是必須使用 id 的方式來 query DOM,Testing Library 預設可以使用 getByTestId 這個方法來找出在 DOM 元素帶有特定 data-testid 的 HTML 元素。

舉例來說,可以在想要 query 到的 DOM 元素加上 data-testid="heading"

<h1 data-testid="heading">Your current path is {location.pathname}</h1>

這時候在寫測試時,就可以使用 getByTestId 這個方法:

import { render, screen } from './custom-testing-library';
import App from './App';

test('can find the specific text in specific DOM', async () => {
  render(<App />);

  const headingElement = screen.getByTestId('heading');
  expect(headingElement).toBeInTheDocument();
});

這麼做雖然很方便,但在 Production 的產品上,總是不希望留下這些 data-testid,一來不太好看,二來實在是給爬蟲一個很大的方便。因此,如果希望能在 production 時移除 data-testid 這個屬性,在官方文件中提到可以透過 babel 的 babel-plugin-react-remove-properties 來解決,這個 plugin 可以在 bundle 成 production 時,把所有指定的 attribute 的移掉,因此我們也可以利用這個 plugin 來把為了測試而寫的 data-testid 移除。

使用時需要在 babel 的設定檔(.babelrc)中加上下列設定:

// .bablerc
{
  "env": {
    "production": {
      "plugins": ["react-remove-properties"]
    }
  }
}

預設就會把名稱是以 data-test 開頭的屬性都移除,但如果讀者不是用預設的 data-testid 作為 query 的屬性,或者你想透過這個 babel plugin 移除掉其他的 DOM attribute 也是可以的,方式也很簡單,可以直接參考該套件的使用說明

小結

在這次的鐵人賽中,筆者僅用大約一週的時間分享撰寫測試時的一些經驗,實際上測試能撰寫的內容,不論是概念或實務都遠超過此,在此次鐵人賽中也有多位參賽者是撰寫和測試有關的題目,如果讀者對於測試想要有更多了解,也歡迎去閱讀這些內容。

最後,還是鼓勵大家從「為你自己開始寫測試」,程式是你寫的,而你必須為你寫的程式負責。

參考資料


上一篇
[Day28] 測試依賴外層 Context Provider 的 React 元件:客製化 render 函式
下一篇
[Day30] 淺談重構(refactoring)與兩把刷子
系列文
React 前端工程師的兩把刷子30

尚未有邦友留言

立即登入留言