Storybook is an incredible tool for building component based frontend applications. It helps you develop the parts of your application in isolation and apply some powerful plugins to ensure quality and consistency. With a recent release of Storybook, there is a new way that we can define our stories that can help us eliminate duplication in other areas of our codebase.
Component Story Format
Traditionally, Storybook stories look like the following code block:
import React from 'react';
import { storiesOf } from '@storybook/react';
import Card from './Card';
storiesOf('Card', module).add('default', () => {
return <Card>Something</Card>
});
These work well and this traditional format is not going away, however, there are some extra benefits we get by using the new component story format.
The new component story format looks like this:
export default { title: "activityFeed/ActivityFeedItem" };
export const standard = () => (
<ActivityFeedItem
name="Bill Murray"
conferenceName="Some Conference"
imageUrl="https://www.fillmurray.com/128/128"
/>
)
You may notice that the only Storybook specific item is the default export. The default export is a JavaScript object that takes a title that can be either the title of the story or the path to the story (this example is using a path) and some additional options.
The story definition is now a standard arrow function.
Benefits
One of the most immediate benefits that I've found when using the Component Story Format is testing. My tests can now reuse the stories.
I've traditionally had code in my tests that was very similar to the code in my stories for wiring up components (notice that the use of the ActivityFeedItem in this test is very similar to the story above):
import React from 'react';
import { render, getByText } from '@testing-library/react';
import ActivityFeed from './ActivityFeed';
it('has Bill Murray', () => {
const { container } = render(
<ActivityFeedItem
name='Bill Murray'
conferenceName='Some Conference'
imageUrl='https://www.fillmurray.com/128/128'
/>
);
expect(getByText(container, 'Bill Murray is speaking at')).toBeDefined();
})
Using the new format, we can leverage the stories we have already created by importing them into our tests:
import React from 'react';
import { render, getByText } from '@testing-library/react';
// import our component from storybook
// instead of re-wiring a new component for the test
import { standard } from './ActivityFeed.stories';
it('has Bill Murray', () => {
const { container } = render(standard());
expect(getByText(container, 'Bill Murray is speaking at')).toBeDefined();
})
This is especially helpful when you have a component that has multiple states. You can make a story that represents each of the various states and import these stories directly in your tests.
(Very contrived code - not real world scenario BUT should help show the concept):
// ActivityFeed.stories.js
export default { title: 'activityFeed/ActivityFeedItem' };
export const withBillMurray = () => (
<ActivityFeedItem
name='Bill Murray'
conferenceName='Some Conference'
imageUrl='https://www.fillmurray.com/128/128'
/>
)
export const withNicolasCage = () => (
<ActivityFeedItem
name='Nicolas Cage'
conferenceName='Some Conference'
imageUrl='https://www.placecage.com/128/128'
/>
)
// ActivityFeed.test.js
import { render, getByText } from '@testing-library/react';
import { withBillMurray, withNicolasCage } from './ActivityFeed.stories';
it('has Bill Murray', () => {
const { container } = render(withBillMurray());
expect(getByText(container, 'Bill Murray is speaking at')).toBeDefined();
})
it('has Nicolas Cage', () => {
const { container } = render(withNicolasCage());
expect(getByText(container, 'Nicolas Cage is speaking at')).toBeDefined();
})
This technique also works with tools such as Cypress
I'd love to know your thoughts or of any other way that you're achieving better productivity in front-end development with similar strategies.
Top comments (0)