測試複合組件
在 2021 年 1 月,特斯拉召回了 158,000 輛汽車,因為一個模組(顯示器)故障。若顯示主控台故障,您將無法使用倒車攝影機、方向燈或駕駛輔助系統。這會顯著增加撞車的風險。
一個有缺陷的模組升級成重大故障。
UI 也面臨類似的挑戰,因為應用程式就像汽車一樣,是由相互連接的零件網路組成。一個組件中的錯誤會影響其周圍的所有其他組件。更不用說應用程式中使用它的每個部分。測試 UI 組件如何組合有助於您防止此類錯誤。
測試 UI 中更複雜的部分很棘手。它們是由組合許多更簡單的組件而創建的,並且也連接到應用程式狀態。在本章中,我們將研究如何隔離複合組件並對其應用視覺化測試。在此過程中,您將學習有關模擬資料和模擬應用程式邏輯的知識。以及測試組件整合的方法。
小錯誤最終會破壞應用程式
應用程式是透過將組件互相連結而建構的。這表示一個元素中的錯誤可能會影響其鄰近元素。例如,重新命名 prop 可能會中斷從父組件到子組件的資料流。或者,UI 元素中不正確的 CSS 通常會導致版面配置損壞。
以 Storybook 的設計系統中的 Button 組件為例。它在多個頁面中被使用了無數次。Button
中的錯誤將會不經意地導致所有這些頁面中出現錯誤。換句話說,一個故障可能會呈指數級增長。當您向上移動組件層次結構朝向頁面級別時,這些錯誤的影響會增加。因此,我們需要一種方法來及早發現此類級聯問題並找出根本原因。
組合測試
視覺化測試透過捕捉和比較 story 的影像快照(在真實瀏覽器中)來捕捉錯誤。這使其非常適合用於發現 UI 變更並識別根本原因。以下快速回顧一下此過程
- 🏷 隔離組件。使用 Storybook 一次測試一個組件。
- ✍🏽 寫出測試案例。每個組件狀態都使用 props 重現。
- 🔍 手動驗證每個測試案例的外觀。
- 📸 使用視覺迴歸測試自動捕捉錯誤。
組合測試完全是在由多個更簡單的組件組成的樹狀結構中較高的「複合」組件上執行視覺化測試。這樣,您就可以量化任何變更可能對整個應用程式產生的影響。並確保系統整體運作正常。
主要的區別在於,複合組件追蹤應用程式狀態並將行為向下傳遞到樹狀結構中。在編寫測試案例時,您必須考慮到這些。
讓我們透過為 TaskList
組件編寫測試來看看這個過程的實際運作情況,該組件顯示屬於使用者的完整任務清單。
它將已釘選的任務移至清單頂部。並具有載入和空白狀態。我們將從為所有這些情境編寫 story 開始。
建立 story 檔案,註冊 TaskList
組件並加入預設案例的 story。
import TaskList from './TaskList';
import * as TaskStories from './Task.stories';
export default {
component: TaskList,
title: 'TaskList',
argTypes: {
...TaskStories.argTypes,
},
};
export const Default = {
args: {
tasks: [
{ id: '1', state: 'TASK_INBOX', title: 'Build a date picker' },
{ id: '2', state: 'TASK_INBOX', title: 'QA dropdown' },
{
id: '3',
state: 'TASK_INBOX',
title: 'Write a schema for account avatar component',
},
{ id: '4', state: 'TASK_INBOX', title: 'Export logo' },
{ id: '5', state: 'TASK_INBOX', title: 'Fix bug in input error state' },
{
id: '6',
state: 'TASK_INBOX',
title: 'Draft monthly blog to customers',
},
],
},
};
請注意 argTypes
。Args (參數) 是 Storybook 用於定義 story 輸入的機制。將它們視為與框架無關的 props。在組件層級定義的 Args (參數) 會自動向下傳遞到每個 story。在我們的案例中,我們使用 Actions 插件定義了三個事件處理程序。
當您與 TaskList
互動時,這些模擬動作將顯示在插件面板中。讓您可以驗證組件是否已正確連接。
組合 Args (參數)
就像您組合組件以建立新的 UI 一樣,您可以組合 Args (參數) 以建立新的 story。複合組件的 Args (參數) 通常甚至會組合其子組件的 Args (參數)。
事件處理程序 Args (參數) 已在 Task stories 檔案中定義,我們可以重複使用。同樣地,我們也可以使用預設 story 中的 Args (參數) 來建立已釘選任務 story。
import TaskList from './TaskList';
import * as TaskStories from './Task.stories';
export default {
component: TaskList,
title: 'TaskList',
argTypes: {
...TaskStories.argTypes,
},
};
export const Default = {
args: {
tasks: [
{ id: '1', state: 'TASK_INBOX', title: 'Build a date picker' },
{ id: '2', state: 'TASK_INBOX', title: 'QA dropdown' },
{
id: '3',
state: 'TASK_INBOX',
title: 'Write a schema for account avatar component',
},
{ id: '4', state: 'TASK_INBOX', title: 'Export logo' },
{ id: '5', state: 'TASK_INBOX', title: 'Fix bug in input error state' },
{
id: '6',
state: 'TASK_INBOX',
title: 'Draft monthly blog to customers',
},
],
},
};
export const WithPinnedTasks = {
args: {
tasks: [
{
id: '6',
title: 'Draft monthly blog to customers',
state: 'TASK_PINNED',
},
...Default.args.tasks.slice(0, 5),
],
},
};
export const Loading = {
args: {
tasks: [],
loading: true,
},
};
export const Empty = {
args: {
...Loading.args,
loading: false,
},
};
透過 Args (參數) 組合來塑造 story 是一種強大的技術。它讓我們能夠編寫 story,而無需一遍又一遍地重複相同的資料。更重要的是,它可以測試組件整合。如果您重新命名其中一個 Task
組件 props,則會導致 TaskList
的測試案例失敗。
到目前為止,我們只處理了透過 props 接受資料和回呼的組件。當您的組件連接到 API 或具有內部狀態時,事情會變得更加棘手。接下來,我們將研究如何隔離和測試此類連接的組件。
有狀態的複合組件
InboxScreen
使用自訂 Hook 從 Taskbox API 擷取資料並管理應用程式狀態。與單元測試非常相似,我們希望將組件與真實後端分離,並隔離測試功能。
這就是 Storybook 插件的用武之地。它們讓您可以模擬 API 請求、狀態、上下文、提供程序以及組件依賴的任何其他內容。The Guardian 和 Sidewalk Labs (Google) 的團隊使用它們來隔離建構整個頁面。
對於 InboxScreen,我們將使用 Mock Service Worker (MSW) 插件 在網路層級攔截請求並傳回模擬回應。
這已在 簡介章節中傳輸的範本中提供。我們需要設定它。讓我們看看如何操作。
執行以下命令以在您的 public
資料夾中產生新的 Service Worker。
yarn init-msw
💡 Public 目錄可能會因專案而異。對於自訂配置,我們建議閱讀 MSW 的文件,以了解有關它們的更多資訊。若要查看 Storybook 中反映的變更,您需要更新 .storybook/main.js
中的 staticDirs
配置元素。
在您的 .storybook/preview.js
檔案中啟用 MSW 插件
import '../src/index.css';
+ import { initialize, mswLoader } from 'msw-storybook-addon';
+ // Initialize MSW
+ initialize();
/** @type { import('@storybook/react').Preview } */
const preview = {
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
+ loaders: [mswLoader],
};
export default preview;
最後,重新啟動 yarn storybook
命令。我們都已設定好在 stories 中模擬 API 請求。
InboxScreen
呼叫 useTasks
Hook,後者又從 /tasks
端點擷取資料。我們可以使用 msw
參數指定模擬回應。請注意,您可以為每個 story 傳回不同的回應。
import { http, HttpResponse } from 'msw';
import InboxScreen from './InboxScreen';
import { Default as TaskListDefault } from './components/TaskList.stories';
export default {
component: InboxScreen,
title: 'InboxScreen',
};
export const Default = {
parameters: {
msw: {
handlers: [
http.get('/tasks', () => {
return HttpResponse.json(TaskListDefault.args);
}),
],
},
},
};
export const Error = {
args: {
error: 'Something',
},
parameters: {
msw: {
handlers: [
http.get('/tasks', () => {
return HttpResponse.json([]);
}),
],
},
},
};
狀態有很多不同的形式。某些應用程式使用 Redux 和 MobX 等程式庫全域追蹤狀態。或透過發出 GraphQL 查詢。或者它們可能會使用容器組件。Storybook 非常靈活,足以支援所有這些情境。如需更多資訊,請參閱:用於管理資料和狀態的 Storybook 插件。
隔離建構組件可以減少開發的複雜性。您不必啟動後端、以使用者身分登入並在 UI 中點擊來偵錯某些 CSS。您可以將所有內容設定為 story 並開始使用。您甚至可以在這些 stories 上執行自動化迴歸測試。
捕捉錯誤回歸
在前一章中,我們設定了 Chromatic 並回顧了基本工作流程。現在我們有了所有複合組件的 stories,我們可以透過執行以下命令來執行視覺化測試
npx chromatic --project-token=<project-token>
您應該會看到一個差異,其中包括 TaskList 和 InboxScreen 的 stories。
現在嘗試變更 Task 組件中的某些內容,例如字型大小或背景顏色。然後提交變更並重新執行 Chromatic。
應用程式的樹狀結構性質表示,對 Task 組件的任何調整也將被更高層級組件的測試捕捉到。組合測試讓您可以了解每個小變更的潛在影響。
驗證組件功能
接下來,我們將超越外觀,進入測試互動。當使用者勾選任務時,您如何確保已觸發適當的事件並且狀態已正確更新?