如何使用 Storybook 测试 UI
Storybook 的 Stories 是您的 UI 组件在各种状态和配置下的测试用例。借助 Storybook,您可以同时以多种方式开发和测试组件,并获得即时反馈。
Storybook 从整体角度处理 UI 组件测试,确保您不仅能够快速可靠地执行组件测试,还能拥有完善的模式来在整个开发生命周期中开发、调试、维护甚至协作这些测试。
开始使用
如果您的项目正在使用 Vite,您很可能可以使用 Vitest 插件在 Storybook 中运行组件测试。该插件基于 Vitest 构建,Vitest 是一款快速轻量级的测试运行器,可与 Vite 无缝协作。
运行此命令,它将安装并配置 Vitest 插件和 Vitest
npx storybook add @storybook/addon-vitest
完整的安装说明,包括项目要求,可在Vitest 插件文档中找到。
项目设置完成后,您将在侧边栏底部看到一个测试小部件。运行测试后,您还将在侧边栏项目上看到测试状态指示器。此外,许多测试可以使用插件面板进行调试。
如果您的项目无法使用 Vitest 插件,您仍然可以使用 test-runner 运行测试。
接下来,我们将介绍 Storybook 中测试的一些关键概念。
关键概念
Storybook 中的测试与其他测试环境类似。您一直在使用的大部分知识和技术在这里也适用。但也有一些显著改进。
组件测试
组件测试是一种测试,它
- 在浏览器中以高保真方式渲染组件
- 模拟用户与实际 UI 交互,类似于端到端 (E2E) 测试
- 只测试 UI 的一个单元(例如单个组件),并且可以深入实现内部来模拟或操作数据,类似于单元测试
结合使用真实浏览器、模拟行为和模拟,为测试 UI 的功能方面提供了强大的手段。
在 Storybook 中,整个测试体验都围绕着组件测试构建。这意味着您可以在与 stories 相同的环境中运行测试,使用相同的工具和技术。
Storybook Test
Storybook Test 通过 Vitest 插件实现了 stories 的实时测试。它使用 Vitest 插件自动将您的 stories 转换为真实的 Vitest 测试,然后使用 Vitest 的浏览器模式运行这些测试。
监听模式
使用监听模式在开发过程中获得即时测试反馈。它会监听您的代码(组件源代码或测试代码)的变化,并自动重新运行相关的测试。这非常适合测试驱动开发,即先编写测试,然后再编写组件。
要激活监听模式,请在测试小部件中按下监听模式按钮(眼睛图标)
CI
如果您没有将 Storybook Test 作为 CI 的一部分运行,您就错过了它带来的最大好处:在合并 PR 之前捕获 bug。
如果您已经在 CI 中运行了 vitest
,那么当您提交更改到 Git 时,您的 stories 应该会自动“免费”作为测试运行。
如果您尚未在 CI 中运行 Vitest,则应该进行设置。首先,在您的 package.json
中添加一个新脚本
{
"scripts": {
"test-storybook": "vitest --project=storybook"
}
}
请注意,这假设您的 stories 有一个名为“storybook”的 Vitest 项目,这是安装 Storybook Test 时的默认配置。如果您已重命名,请相应地调整脚本。
接下来,添加一个新的 CI 工作流程。
如果您使用 Github Actions,它会看起来像这样
name: Storybook Tests
on: [push]
jobs:
test:
runs-on: ubuntu-latest
container:
# Make sure to grab the latest version of the Playwright image
# https://playwright.net.cn/docs/docker#pull-the-image
image: mcr.microsoft.com/playwright:v1.52.0-noble
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22.12.0
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm run test-storybook
如果您使用不同的 CI 提供商,请查阅我们完整的CI 指南以获取更多信息。
Storybook Test 默认使用 Playwright 渲染您的 stories。为了获得最快的体验,您应该使用已安装 Playwright 的机器镜像(如上面片段所示)。
覆盖率
代码覆盖率提供了关于哪些代码被测试过或未被测试过的洞察。它在您开发时起到护栏的作用,帮助确保您的工作被适当的测试覆盖。
在 Storybook Test 中,您可以通过两种方式查看 stories 提供的覆盖率。首先,在测试小部件中显示摘要。其次,点击该覆盖率摘要将打开一个完整的交互式报告。
在报告中,点击进入特定组件会显示代码中哪些精确的分支被覆盖或未被覆盖
每个项目的覆盖率报告看起来都会不同,但需要注意的重要事项是
- 总行/分支覆盖率,它作为高级别的健康检查。
- 未覆盖的特定行/分支,它们是潜在的测试空白。
在我们看来,目标不是达到 100% 覆盖率并填补所有空白,而是提供一个衡量标准来帮助您判断代码/测试变更,并让这些空白告知您未测试的关键状态或交互。例如,如果您正在构建原型,您可以完全跳过测试,而在关键应用中,您可能需要仔细检查每一个细节。
运行覆盖率分析会减慢您的测试运行速度,因此默认情况下它是关闭的。要激活覆盖率,请在测试小部件中勾选覆盖率复选框。
CI 中的覆盖率
在我们查看覆盖率的同时,更新您的 CI 工作流程以包含它
- yarn test
+ yarn test --coverage
为什么我们要运行所有测试 (yarn test
) 而不是只运行 Storybook 测试 (yarn test-storybook
)?因为覆盖率报告在计算项目中的所有测试时最准确,而不仅仅是您编写的 stories。
查看Storybook 特定的覆盖率可能会有帮助,但在 CI 输出中,您希望看到项目的全面覆盖率。
这样我们就可以跟踪每个 PR 中的覆盖率变化。
这些是您在 Storybook 中测试所需的关键概念。现在,让我们看看您可以执行的不同类型的测试。
测试类型
Storybook Test 支持多种测试类型,帮助您从多个维度验证您的工作。
渲染测试
在 Storybook 中测试组件最重要的工具是 stories,它们以各种状态渲染您的组件。
然而,您可能不知道一个基本的 story 也是一个冒烟测试,我们称之为渲染测试。当 story 成功渲染时测试通过,当出错时测试失败。
根据组件的复杂性,您可能能够通过这种方式捕获许多关键的 UI 状态。
交互测试
渲染测试是一种非常基本的交互测试。为了测试有状态组件或验证交互行为,您可以为您的 story 定义一个 play 函数
// Replace your-framework with the name of your framework (e.g. react-vite, vue3-vite, etc.)
import type { Meta, StoryObj } from '@storybook/your-framework';
import { expect } from 'storybook/test';
import { Dialog } from './Dialog';
const meta = {
component: Dialog,
} satisfies Meta<typeof Dialog>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Opens: Story = {
play: async ({ canvas, userEvent }) => {
// Click on a button and assert that a dialog appears
const button = canvas.getByRole('button', { text: 'Open Modal' });
await userEvent.click(button);
await expect(canvas.getByRole('dialog')).toBeInTheDocument();
},
};
但是 play
函数还可以用于设置状态、创建间谍、模拟网络、模拟用户与组件的交互、断言输出等。它们是测试的核心,也是您在 Storybook 中其余测试旅程的基础。
这里有一个更复杂的例子,其中包含通过 fn
工具进行的间谍和模拟。
// Replace your-framework with the name of your framework (e.g. react-vite, vue3-vite, etc.)
import type { Meta, StoryObj } from '@storybook/your-framework';
import { fn, expect } from 'storybook/test';
import { users } from '#mocks';
import { EventForm } from './EventForm';
const meta = {
component: EventForm,
} satisfies Meta<typeof EventForm>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Submits: Story = {
// Mock functions so we can manipulate and spy on them
args: {
getUsers: fn(),
onSubmit: fn(),
},
beforeEach: async ({ args }) => {
// Manipulate `getUsers` mock to return mocked value
args.getUsers.mockResolvedValue(users);
},
play: async ({ args, canvas, userEvent }) => {
const usersList = canvas.getAllByRole('listitem');
await expect(usersList).toHaveLength(4);
await expect(canvas.getAllByText('VIP')).toHaveLength(2);
const titleInput = await canvas.findByLabelText('Enter a title for your event');
await userEvent.type(titleInput, 'Holiday party');
const submitButton = canvas.getByRole('button', { text: 'Plan event' });
await userEvent.click(submitButton);
// Spy on `onSubmit` to verify that it is called correctly
await expect(args.onSubmit).toHaveBeenCalledWith({
name: 'Holiday party',
userCount: 4,
data: expect.anything(),
});
},
};
有关如何使用 play
函数编写交互测试和模拟的更多信息,请参阅交互测试指南。
可访问性测试
Storybook 的可访问性 (A11y) 插件对您的 stories 运行一组自动化检查,以帮助确保您的组件可以被所有用户使用,无论他们的能力或使用的技术如何。这意味着支持诸如:键盘导航、屏幕阅读器支持、可用的颜色对比度等要求。
可访问性不仅是正确的事情,而且日益成为强制要求。例如,欧洲可访问性法案计划于 2025 年 6 月生效。同样在美国,诸如美国残疾人法案 (ADA) 和康复法案第 508 条等法律适用于许多面向公众的服务。
要在组件测试的同时激活可访问性检查,请在测试小部件中勾选可访问性复选框。
激活后,您将在侧边栏中看到可访问性测试失败项。
任何失败都可以在可访问性插件面板中进行调试。
视觉测试
视觉测试是测试组件最高效的方式。只需点击一个按钮,您就可以获取 Storybook 中每个 story 的快照,并将这些快照与基线(最后已知的“良好”快照)进行比较。这不仅可以让您检查组件的外观,还可以无需编写或维护任何测试代码来检查组件功能的一大部分!
Storybook 使用 Storybook 团队构建的云服务 Chromatic 原生支持跨浏览器视觉测试。启用视觉测试后,每个 story 会自动转换为测试。这让您直接在 Storybook 中获得 UI bug 的即时反馈。
快照测试
在大多数情况下,其他测试类型会以较少的精力提供更多的覆盖范围。但在某些场景下,将 story 渲染的标记与已知基线进行比较会很有帮助。例如,它可以帮助识别导致渲染错误的标记变化。
在其他测试工具中重用 stories
Stories 被设计为可重用的,因此您可以在流行的测试工具中将它们用作测试用例。
端到端
有时您需要测试完整的流程,包括完整的运行堆栈。在这种情况下,您仍然可以在 Playwright 或 Cypress 端到端 (E2E) 测试中导入并使用您的 stories。
单元
如果您愿意,可以在传统的测试环境(如 Vitest 或 Jest)中重用您的 Storybook stories。
更多测试资源