文档测试
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 的一个单元(例如,单个组件),**并且**可以深入到实现以模拟(mock)事物或操作数据,这类似于单元测试

这种结合使用真实浏览器、模拟行为和模拟(mocking)提供了强大的测试 UI 功能的方法。

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

Storybook 测试

Storybook Test 通过 Vitest 插件 实现对您的故事进行实时测试。它使用 Vitest 插件自动将您的故事转换为真正的 Vitest 测试,然后通过 Vitest 的浏览器模式运行。

观察模式

通过观察模式,在开发过程中获得即时测试反馈。它会监视您的代码——无论是组件源文件还是测试文件——的变化,并自动仅重新运行相关的测试。这非常适合测试驱动开发,即您先编写测试,然后再编写组件。

要激活观察模式,请点击测试小组件中的观察模式按钮(眼睛图标)

Testing widget with watch mode enabled

CI

如果您没有将 Storybook Test 作为 CI 的一部分运行,您将错过它提供的最大优势:在合并 PR 之前就能发现 Bug。

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

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

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

请注意,这假设您有一个名为“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 渲染您的故事。为了获得最快的体验,您应该使用 已预装 Playwright 的机器镜像(如上面的代码片段所示)。

覆盖率

代码覆盖率可以了解哪些代码被测试了,哪些没有。它在您开发过程中充当安全网,帮助确保您的工作得到适当的测试覆盖。

在 Storybook Test 中,您可以通过两种方式查看您的故事提供的覆盖率。首先,测试小组件中会显示摘要。其次,点击该覆盖率摘要将打开一个完整的交互式报告。

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)?因为覆盖率报告在考虑项目中的所有测试(而不仅仅是您编写的故事)时最准确。

查看 Storybook 特定的覆盖率 很有帮助,但在 CI 输出中,您希望看到项目的全面覆盖率。

这样我们就可以在每个 PR 中跟踪覆盖率的变化。

以上是您在 Storybook 中测试所需掌握的关键概念。现在,让我们来看看您可以执行的不同类型的测试。

测试类型

Storybook Test 支持各种测试类型,以帮助您从多个维度验证您的工作。

渲染测试

测试 Storybook 中组件的最重要工具是能以各种状态渲染您组件的故事。

然而,您可能没有意识到,一个基本的故事也是一个 冒烟测试,我们称之为 **渲染测试**。当故事成功渲染时测试通过,当它出错时测试失败。

根据您组件的复杂性,您可以通过这种方式捕获许多关键的 UI 状态。

交互测试

渲染测试是一种非常基础的交互测试。要测试有状态的组件或验证交互行为,请为您的故事定义一个 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/users';
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) 插件 会对您的故事运行一组自动化检查,以帮助确保您的组件能够被所有用户使用,无论其能力或所使用的技术如何。这意味着支持以下要求:键盘导航、屏幕阅读器支持、可用的颜色对比度等。

可访问性不仅是正确的做法,而且日益成为一项强制性要求。例如,《欧洲无障碍法案》计划于 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 中每个故事的快照,并将这些快照与基线进行比较——即上次已知的“良好”快照。这不仅允许您检查组件的外观,而且它们还能够检查很大一部分组件功能,**而无需编写或维护任何测试代码**!

Storybook 支持跨浏览器视觉测试,原生使用 Chromatic,这是一个由 Storybook 团队创建的云服务。当您启用视觉测试时,每个故事都会自动转换为一个测试。这样您就能直接在 Storybook 中获得 UI Bug 的即时反馈。

Visual test panel with diff

快照测试

在大多数情况下,其他测试类型可以以更少的精力提供更多的覆盖。但在某些情况下,将故事的渲染标记与已知基线进行比较会很有帮助。例如,它可以帮助识别触发渲染错误的标记更改。

在其他测试工具中重用故事

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

端到端

有时您需要测试一个完整的流程,包含完整的运行堆栈。在这些情况下,您仍然可以通过在 Playwright 或 Cypress 端到端 (E2E) 测试中导入您的故事来使用它们。

单元

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

更多测试资源