ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

在 Storybook 单元测试中复用 Story 的 Args:用 composeStories / composeStory 断言组件行为

在 Storybook 单元测试中复用 Story 的 Args:用 composeStories / composeStory 断言组件行为 在 Storybook 单元测试中复用 Story 的 Args用 composeStories / composeStory 断言组件行为在 Storybook 的 portable stories便携式组件测试方案里通过composeStories与composeStory把*.stories.*文件中的场景组合成可渲染组件后这些组件不仅能在测试里被渲染还完整携带了来自 story、meta 与全局配置合并后的args。当你在单测里遇到“Story 中定义的参数没有传给测试、期望值只能靠手写重复”的困惑时正确的做法不是复制粘贴常量而是直接从组合后的组件属性Primary.args读取断言依据。本文结合 Storybook 仓库的官方文档与源码测试梳理 React 与 Vue3 两种渲染器下的完整写法并解释其中的底层机制。这篇指南解决什么问题docs/writing-tests/integrations/stories-in-unit-tests.mdx在“Troubleshooting → The args are not being passed to the test”小节中收录了本主题对应的代码示例 reuse-args-test.md。它本质上要处理两类诉求消除重复Story 文件里已经写明了组件在该场景下的 props即args测试若再手写一遍相同期望值Story 一改、测试就失真形成维护负担。直接复用 story 输入composeStories(stories)得到的每个故事对象如Primary本身带有解析并合并好的args属性可以直接作为测试断言的数据源。前置知识portable stories 中 args 从哪来按官方文档的定义composeStories能把*.stories.js|ts中的命名导出story转换为可在 Node 测试环境配合 JSDOM中渲染的组件元素同时把你在项目中开启的 Storybook 特性一并应用进去例如 decorators文档内部链接仓库内对应概念见docs/writing-tests/integrations/stories-in-unit-tests.mdx与args。这样做的目的是让测试始终与 Story 保持同步无需重写场景。关于 args 的关键结论是组合后的 story 组件不是普通组件。从 React 渲染器的实现看code/renderers/react/src/portable-stories.tsx中导出了composeStory约 L106与composeStories约 L148组合出的对象既可以被 JSX 渲染又暴露了合并后的属性。因此在测试里你能写出Primary.args.label这样的访问表达式——它拿到的值是 Story、Metadefault export以及项目级全局配置合并后的最终结果。核心方案直接从 composed story 读取 args示例 reuse-args-test.md 给出了 React 与 Vue3 各两种JS / TS写法覆盖了绝大多数渲染器组合。下面按官方示例逐一带上说明。ReactJS/JSX 与 TS/TSXcomposeStories返回的对象中每个 story 都是可直接作为 JSX 使用的组件同时挂有argsimport { render, screen } from testing-library/react; // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. import { composeStories } from storybook/your-framework; import * as stories from ./Button.stories; const { Primary } composeStories(stories); test(reuses args from composed story, () { render(Primary /); const buttonElement screen.getByRole(button); // Testing against values coming from the story itself! No need for duplication expect(buttonElement.textContent).toEqual(Primary.args.label); });import { render, screen } from testing-library/react; // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. import { composeStories } from storybook/your-framework; import * as stories from ./Button.stories; const { Primary } composeStories(stories); test(reuses args from composed story, () { render(Primary /); const buttonElement screen.getByRole(button); // Testing against values coming from the story itself! No need for duplication expect(buttonElement.textContent).toEqual(Primary.args.label); });使用要点导入路径按实际框架替换例如storybook/react-vite、storybook/nextjs、storybook/nextjs-vite仓库内各渲染器对应路径可参考 code/frameworks 与 code/renderers 目录。const { Primary } composeStories(stories)从整个 story 模块中解构出命名导出。断言时通过Primary.args.label读取“这张 Story 为组件设定的 props 值”这样组件文案变化时只需更新 Story 文件测试期望自动跟随。Vue3JS 与 TSVue 版本与 React 的差异在于渲染方式storybook/vue3-vite的 composed story 是通过render(Primary())挂载的而不是写成 JSX 标签import { render, screen } from testing-library/vue; import { composeStories } from storybook/vue3-vite; import * as stories from ./Button.stories; const { Primary } composeStories(stories); test(reuses args from composed story, () { render(Primary()); const buttonElement screen.getByRole(button); // Testing against values coming from the story itself! No need for duplication expect(buttonElement.textContent).toEqual(Primary.args.label); });import { render, screen } from testing-library/vue; import { composeStories } from storybook/vue3-vite; import * as stories from ./Button.stories; const { Primary } composeStories(stories); test(reuses args from composed story, () { render(Primary()); const buttonElement screen.getByRole(button); // Testing against values coming from the story itself! No need for duplication expect(buttonElement.textContent).toEqual(Primary.args.label); });底层验证这条用法在仓库源码中有对应测试这段示例并非孤立的文档写法而是有真实单测支撑。在 React 渲染器的测试文件 portable-stories.test.tsx 中就存在一条名称与文档示例完全一致的用例const Secondary composeStory(ButtonStories.CSF2Secondary, ButtonStories.default); it(reuses args from composed story, () { render(Secondary /); const buttonElement screen.getByRole(button); expect(buttonElement.textContent).toEqual(Secondary.args.children); });对应文件 portable-stories.test.tsx#L43-L47。它验证了两件事composeStories/composeStory组合出的组件可以直接在测试渲染器里渲染组合对象上的.args属性携带了故事内容能作为断言数据源。同文件还展示了 composed story 的其他能力如 L84-L85 中的LoaderStory.load()与LoaderStory.run()即组合结果也保留loaders与play的执行入口说明这类对象承载的远不止渲染函数本身。常见误区与注意事项“args 没有被传给测试”通常不是没传而是取错了来源Story 中定义的 args 只属于故事文件测试里若想“复用”应读取 composed story 的.args而不是在测试里另行声明一套常量——后者正是该方案要消灭的重复。测试环境必须应用项目级注解官方文档强调你必须先配置测试环境以使用 portable stories即setProjectAnnotations等初始化流程组合出的 story 才会带上 Storybook 配置中的全局 decorators、parameters 等。未完成初始化时组合结果可能与 Storybook 实际渲染表现不一致。查询 API 的选择本文示例位于单元测试文件中composed story 跑在测试渲染器里所以使用 Testing Library 的screen查询是合适的而如果在 story 的play函数内部写交互则推荐使用框架提供的canvas查询让交互局限在当前渲染的 story 范围内。覆盖全局配置如果某些测试不希望被preview.*中的全局配置影响例如锁定 locale 的globalTypes、特定 decorators可以通过扩展composeStory/composeStories的方式传入测试专属配置相关方案示例见 override-compose-story-test.md。单条 story 与批量 story只测单个场景时可用composeStory建议传入 default export 即 story 元数据以保证类型与信息正确示例见 single-story-test.md要把多个 story 组合进同一条测试则使用composeStories示例见 multiple-stories-test.md。延伸阅读本篇代码示例的主体文档docs/writing-tests/integrations/stories-in-unit-tests.mdxReact 渲染器中的composeStory/composeStories实现code/renderers/react/src/portable-stories.tsx与示例同名的仓库内测试用例code/renderers/react/src/test/portable-stories.test.tsx完整的 Testing Library 基础测试示例component-test-with-testing-library.md说明以上your-framework/vue3-vite等导入路径需按实际安装的渲染器替换仓库内各框架与渲染器的源码组织可分别在 code/frameworks 与 code/renderers 中查看。创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表