[RFC]: Making Storybook a first-class test framework with CSF factories #29658
Replies: 1 comment
-
Hi! Thanks for opening this RFC. I believe this is a movement in the right direction. Below, I will share some of my thoughts regarding the proposed format (mostly around testing) and also things generally related to API design. Story
|
Beta Was this translation helpful? Give feedback.
-
Storybook has long been a favorite tool for documenting and developing design system components in isolation. Over time, it has evolved to offer more powerful testing features—with hooks such as
play
andbeforeEach
—that position it as a UI testing framework. However, leveraging Storybook for testing often requires learning a new testing syntax and best practices built on top of Storybook’s Component Story Format (CSF).In this guide, we'll explore how CSF factories can transform Storybook into a first-class testing framework. We'll demonstrate how to enable more standard testing capabilities while reducing boilerplate in the args-based CSF syntax.
Reducing CSF Boilerplate with Factories
Writing a type-safe CSF3 story typically involves a significant boilerplate. Here's an example of a standard CSF3 story for a
Button
component:To streamline this process, we can introduce factories that require no extra type annotations for type safety. Here's how the
Button
story would look using factories:This approach reduces the need for explicit type declarations and manual repetitive code, minimizing the chances of errors. It makes the story definitions more concise and easier to understand.
The preview file
The preview file is central to configuring Storybook's behavior and addons. Here's a
.storybook/preview.ts
file using factories:To leverage type information from addons within your stories, explicitly import the preview config:
Note: While you can use relative imports for the preview file, we recommend adopting this standard-based absolute import convention, popularized by Kent C. Dodds. Learn more about this convention
Making Storybook a First-Class Test Framework
While Storybook excels at developing and documenting components, its potential as a testing framework is often underutilized. It's not immediately obvious how to translate tests written with tools like Vitest or Jest into Storybook. Consider the following tests:
We propose a way to write these tests within Storybook in a very similar fashion:
The above is a shortcut for writing a story with only a
play
function. It can be expanded as follows:Note: The mount function is also already available in storybook 8: #27389
We envision that many test users prefer doing everything in the
play
function, rather than using separate CSF constructs such asargs
,component
,decorators
,parameters
, andrender
.In this proposal, users don’t necessarily need any of those constructs. You can still use them—they are powerful abstraction mechanisms—but you are not forced to learn them.
For example, if you want to make the props of your component interactive in Storybook, you can opt-in to using
meta
andargs
:What’s changed here? We’ve substituted the
mount(<RestaurantDetailPage .../>)
with a more abstract construct — we’ve moved thecomponent
up to themeta
and we’ve moved theprops
to theargs
annotation. Why?By telling Storybook explicitly you are “rendering component X with props Y” it allows us to do a bunch of powerful things that the more freeform Jasmine API cannot:
But it is optional. Users can also get lots of benefits from Storybook without this
args
-refactor:Factory for CSF1-style stories
It is popular to write stories as a component when using React:
In this proposal, you will be able to use this pattern with CSF factories as well:
Full control of the test lifecycle
In the example above, you might wonder how to configure
msw
so that it can be used in the play function in Storybook. Where do you start the service worker? How do you reset handlers? For full control of the test lifecycle, we have addedbeforeEach
andbeforeAll
as part of the Component Story Format recently.Setting up MSW with beforeAll and beforeEach
Here is an example how you can fully setup
msw
manually without the need to use the official MSW addon:Proposal to add
afterEach
to storybookWe accept a cleanup callback in both `beforeAll` and `beforeEach`, but we haven’t yet adopted the `afterAll` and `afterEach` hooks.
Those
after
-hooks are tricky in an interactive test tool like Storybook. As a user, you might expectafterEach
to run after the test is executed, and in other interactive test tools like Vitest's browser UI, this is where it runs.But there's a problem here: if you reset MSW handlers after the test is executed, the handlers will be cleaned up when you interact manually with the browser after the test.
For a similar reason, Vitest has forked testing library to ensure dom cleanup is not run at the end of a test: https://github.com/vitest-dev/vitest-browser-react.
We have considered adding
afterEach
and only running when switching to a new story, but then you can not do a generic post-test assertion anymore inafterEach
:For this reason, we recommend cleaning up global mock state at the very beginning of each test, in a global
beforeEach
. For example, runningmsw.resetHandlers()
before each test.If you want to clean up specific mock state constructed in the
beforeEach
of a meta or a story, we accept a cleanup callback:Those cleanup callbacks are not called after the test has executed, but only when switching to a new story. We are considering adding
afterEach
in Storybook for executing post-test assertions:#29583
Parametrizing tests in storybook
In many test frameworks, you can parameterize tests with an
each
method such as:In Storybook, every tests must be an ESM export. This ensures we can position all stories in the sidebar in a performant way. In this proposal, you can parameterize a play function by adding custom
parameters
tometa
:Parameters are static in storybook, you can not change them dynamically in the UI. While args are dynamic and controllable by the control addon. At some point we want to support non-component args prefixed with a
$
. Normally, all args of the story are automatically applied as props of the component. We would like to introduce “$-prefixed-args” (aka non-component args) as a way to get all the power of controls and args for other purposes (aside from component props).All factories accept a generic type parameter. For example, addons can leverage that for adding new Storybook parameters:
Beta Was this translation helpful? Give feedback.
All reactions