加入直播会话:美国东部时间周四上午 11 点,Storybook 9 版本发布及 AMA 问答
文档测试
Storybook Docs

如何使用 Storybook 测试 UI

Storybook 的 Stories 是您的 UI 组件在各种状态和配置下的测试用例。借助 Storybook,您可以同时以多种方式开发和测试组件,并获得即时反馈。

Storybook 从整体角度处理 UI 组件测试,确保您不仅能够快速可靠地执行组件测试,还能拥有完善的模式来在整个开发生命周期中开发、调试、维护甚至协作这些测试。

开始使用

如果您的项目正在使用 Vite,您很可能可以使用 Vitest 插件在 Storybook 中运行组件测试。该插件基于 Vitest 构建,Vitest 是一款快速轻量级的测试运行器,可与 Vite 无缝协作。

运行此命令,它将安装并配置 Vitest 插件和 Vitest

npx storybook add @storybook/addon-vitest

完整的安装说明,包括项目要求,可在Vitest 插件文档中找到。

项目设置完成后,您将在侧边栏底部看到一个测试小部件。运行测试后,您还将在侧边栏项目上看到测试状态指示器。此外,许多测试可以使用插件面板进行调试。

Storybook app with story status indicators, testing widget, and interactions panel annotated

如果您的项目无法使用 Vitest 插件,您仍然可以使用 test-runner 运行测试。

接下来,我们将介绍 Storybook 中测试的一些关键概念。

关键概念

Storybook 中的测试与其他测试环境类似。您一直在使用的大部分知识和技术在这里也适用。但也有一些显著改进。

组件测试

组件测试是一种测试,它

  • 在浏览器中以高保真方式渲染组件
  • 模拟用户与实际 UI 交互,类似于端到端 (E2E) 测试
  • 只测试 UI 的一个单元(例如单个组件),并且可以深入实现内部来模拟或操作数据,类似于单元测试

结合使用真实浏览器、模拟行为和模拟,为测试 UI 的功能方面提供了强大的手段。

在 Storybook 中,整个测试体验都围绕着组件测试构建。这意味着您可以在与 stories 相同的环境中运行测试,使用相同的工具和技术。

Storybook Test

Storybook Test 通过 Vitest 插件实现了 stories 的实时测试。它使用 Vitest 插件自动将您的 stories 转换为真实的 Vitest 测试,然后使用 Vitest 的浏览器模式运行这些测试。

监听模式

使用监听模式在开发过程中获得即时测试反馈。它会监听您的代码(组件源代码或测试代码)的变化,并自动重新运行相关的测试。这非常适合测试驱动开发,即先编写测试,然后再编写组件。

要激活监听模式,请在测试小部件中按下监听模式按钮(眼睛图标)

Testing widget with watch mode enabled

CI

如果您没有将 Storybook Test 作为 CI 的一部分运行,您就错过了它带来的最大好处:在合并 PR 之前捕获 bug。

如果您已经在 CI 中运行了 vitest,那么当您提交更改到 Git 时,您的 stories 应该会自动“免费”作为测试运行。

如果您尚未在 CI 中运行 Vitest,则应该进行设置。首先,在您的 package.json 中添加一个新脚本

package.json
{ 
  "scripts": {
    "test-storybook": "vitest --project=storybook"
  }
}  

请注意,这假设您的 stories 有一个名为“storybook”的 Vitest 项目,这是安装 Storybook Test 时的默认配置。如果您已重命名,请相应地调整脚本。

接下来,添加一个新的 CI 工作流程。

如果您使用 Github Actions,它会看起来像这样
.github/workflows/test-storybook.yml
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 提供的覆盖率。首先,在测试小部件中显示摘要。其次,点击该覆盖率摘要将打开一个完整的交互式报告。

Two browser windows. The frontmost one shows the interactive coverage report generated by the Vitest addon. The background one shows the Storybook sidebar with the coverage summary visible in the testing widget.

在报告中,点击进入特定组件会显示代码中哪些精确的分支被覆盖或未被覆盖

Interactive coverage report generated by the Vitest addon, showing the Page component's reported source

每个项目的覆盖率报告看起来都会不同,但需要注意的重要事项是

  1. 总行/分支覆盖率,它作为高级别的健康检查。
  2. 未覆盖的特定行/分支,它们是潜在的测试空白。

在我们看来,目标不是达到 100% 覆盖率并填补所有空白,而是提供一个衡量标准来帮助您判断代码/测试变更,并让这些空白告知您未测试的关键状态或交互。例如,如果您正在构建原型,您可以完全跳过测试,而在关键应用中,您可能需要仔细检查每一个细节。

运行覆盖率分析会减慢您的测试运行速度,因此默认情况下它是关闭的。要激活覆盖率,请在测试小部件中勾选覆盖率复选框。

Testing widget with coverage activated

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 函数

Dialog.stories.ts
// 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 工具进行的间谍和模拟

EventForm.stories.ts
// 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 条等法律适用于许多面向公众的服务。

要在组件测试的同时激活可访问性检查,请在测试小部件中勾选可访问性复选框。

Testing widget with accessibility activated

激活后,您将在侧边栏中看到可访问性测试失败项。

Storybook showing a failing accessibility test in both the sidebar story menu and the Accessibility panel

任何失败都可以在可访问性插件面板中进行调试。

Storybook app with accessibility panel open, showing violations and an interactive popover on the violating elements in the preview

视觉测试

视觉测试是测试组件最高效的方式。只需点击一个按钮,您就可以获取 Storybook 中每个 story 的快照,并将这些快照与基线(最后已知的“良好”快照)进行比较。这不仅可以让您检查组件的外观,还可以无需编写或维护任何测试代码来检查组件功能的一大部分!

Storybook 使用 Storybook 团队构建的云服务 Chromatic 原生支持跨浏览器视觉测试。启用视觉测试后,每个 story 会自动转换为测试。这让您直接在 Storybook 中获得 UI bug 的即时反馈。

Visual test panel with diff

快照测试

在大多数情况下,其他测试类型会以较少的精力提供更多的覆盖范围。但在某些场景下,将 story 渲染的标记与已知基线进行比较会很有帮助。例如,它可以帮助识别导致渲染错误的标记变化。

在其他测试工具中重用 stories

Stories 被设计为可重用的,因此您可以在流行的测试工具中将它们用作测试用例。

端到端

有时您需要测试完整的流程,包括完整的运行堆栈。在这种情况下,您仍然可以在 Playwright 或 Cypress 端到端 (E2E) 测试中导入并使用您的 stories。

单元

如果您愿意,可以在传统的测试环境(如 Vitest 或 Jest)中重用您的 Storybook stories。

更多测试资源