测试组件交互
您拨动开关,灯没有亮。可能是灯泡烧坏了,也可能是线路故障。开关和灯泡通过墙壁内的电线相互连接。
应用程序也是如此。表面是用户看到和交互的 UI。在幕后,UI 被连接起来以促进数据和事件的流动。
随着您构建更复杂的 UI(如页面),组件除了渲染 UI 外,还承担更多责任。它们获取数据并管理状态。本章将教您如何使用计算机来模拟和验证用户交互。
该组件真的能正常工作吗?
组件的主要任务是根据一组 props 渲染 UI 的一部分。更复杂的组件还会跟踪应用程序状态并将行为传递到组件树中。
例如,一个组件将从初始状态开始。当用户在输入字段中键入内容或单击按钮时,它会在应用程序内触发一个事件。组件会响应此事件更新状态。这些状态更改随后会更新渲染的 UI。这是交互的完整周期。
在 InboxScreen
上,用户可以单击星标图标来固定任务。或者单击复选框来存档它。视觉测试确保组件在所有这些状态下看起来都是正确的。我们还需要确保 UI 正在正确响应这些交互。
Storybook 中的组件测试如何工作?
测试交互是验证用户行为的一种广泛模式。您提供模拟数据来设置测试场景,使用 Testing Library 模拟用户交互,并检查生成的 DOM 结构。
在 Storybook 中,这种熟悉的工作流程发生在您的浏览器中。这使得调试故障更容易,因为您在与开发组件相同的环境(浏览器)中运行测试。
我们将首先编写一个故事来设置组件的初始状态。然后使用 play 函数模拟用户行为,例如单击和表单条目。最后,使用 Storybook 测试运行器来检查 UI 和组件状态是否正确更新。
设置测试运行器
运行以下命令进行安装
yarn add --dev @storybook/test-runner
然后将测试任务添加到项目的 package.json
{
"scripts": {
"test-storybook": "test-storybook"
}
}
最后,启动您的 Storybook(测试运行器将针对您的本地 Storybook 实例运行)
yarn storybook
重用故事作为组件测试用例
在上一章中,我们在 InboxScreen.stories.jsx
文件中编目了 InboxScreen 组件的所有用例。这使我们能够在开发期间进行外观抽查,并通过视觉测试捕获回归。这些故事现在也将为我们的组件测试提供支持。
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([]);
}),
],
},
},
};
使用 play 函数编写组件测试
Testing Library 提供了一个方便的 API 来模拟用户交互——单击、拖动、点击、键入等。而 Vitest 提供了断言实用程序。我们将使用 Storybook 工具化的这两个工具来编写测试。因此,您可以获得熟悉的开发者友好的语法来与 DOM 交互,但具有额外的遥测功能来帮助调试。
测试本身将放在 play 函数内。此代码片段附加到故事并在故事渲染后运行。
让我们添加我们的第一个组件测试,以验证用户是否可以固定任务
import { http, HttpResponse } from 'msw';
import InboxScreen from './InboxScreen';
import { Default as TaskListDefault } from './components/TaskList.stories';
import { expect, userEvent, findByRole, within } from '@storybook/test';
// ... code omitted for brevity ...
export const PinTask = {
parameters: {
...Default.parameters,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const getTask = (id) => canvas.findByRole('listitem', { name: id });
const itemToPin = await getTask('task-4');
// Find the pin button
const pinButton = await findByRole(itemToPin, 'button', { name: 'pin' });
// Click the pin button
await userEvent.click(pinButton);
// Check that the pin button is now a unpin button
const unpinButton = within(itemToPin).getByRole('button', {
name: 'unpin',
});
await expect(unpinButton).toBeInTheDocument();
},
};
💡 @storybook/test
包取代了 @storybook/jest
和 @storybook/testing-library
测试包,提供了更小的捆绑包大小和基于 Vitest 包的更直接的 API。
每个 play 函数都接收 Canvas 元素——故事的顶层容器。您可以将查询范围限定在此元素内,从而更容易找到 DOM 节点。
在本例中,我们正在寻找“Export logo”任务。然后在其中找到固定按钮并单击它。最后,我们检查按钮是否已更新为未固定状态。
当 Storybook 完成故事渲染时,它会执行 play 函数中定义的步骤,与组件交互并固定任务——类似于用户执行的操作。如果您查看您的交互面板,您将看到逐步流程。它还提供了一组方便的 UI 控件,用于暂停、恢复、倒带和单步执行每个交互。
使用测试运行器执行测试
现在我们已经完成了第一个测试,让我们继续为存档和编辑任务功能添加测试。
// ... code omitted for brevity ...
export const ArchiveTask = {
parameters: {
...Default.parameters,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const getTask = (id) => canvas.findByRole('listitem', { name: id });
const itemToArchive = await getTask('task-2');
const archiveButton = await findByRole(itemToArchive, 'button', {
name: 'archiveButton-2',
});
await userEvent.click(archiveButton);
},
};
export const EditTask = {
parameters: {
...Default.parameters,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const getTask = (id) => canvas.findByRole('listitem', { name: id });
const itemToEdit = await getTask('task-5');
const taskInput = await findByRole(itemToEdit, 'textbox');
await userEvent.type(taskInput, ' and disabled state');
await expect(taskInput.value).toBe(
'Fix bug in input error state and disabled state'
);
},
};
您现在应该看到这些场景的故事。Storybook 仅在您查看故事时运行组件测试。因此,您必须查看每个故事才能运行所有检查。
每次进行更改时手动查看整个 Storybook 是不现实的。Storybook 测试运行器自动化了该过程。它是一个独立的实用程序——由 Playwright 提供支持——它运行您的所有测试并捕获损坏的故事。
启动测试运行器(在单独的终端窗口中)
yarn test-storybook --watch
它将验证是否所有故事都渲染且没有任何错误,并且所有断言都已通过。如果测试失败,您将获得一个链接,该链接将在浏览器中打开失败的故事。
总而言之,设置代码和测试都位于 stories 文件中。使用 play 函数,我们像用户一样与 UI 进行了交互。Storybook 组件测试将实时浏览器的直观调试环境与无头浏览器的高性能和可脚本化性相结合。
捕捉可用性问题
当您确保每个用户都能使用您的 UI 时,您会影响业务财务并满足 法律要求。这是一个双赢的局面。下一章演示了如何利用故事的可移植性来简化可访问性测试。